Compare commits
193 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f50b198c21 | ||
|
|
3495326de3 | ||
|
|
b5973085e7 | ||
|
|
8687214420 | ||
|
|
d61b89a1e5 | ||
|
|
468b42abd2 | ||
|
|
fc03e5f983 | ||
|
|
c4742e38ea | ||
|
|
99e1adbe13 | ||
|
|
eb5c797a43 | ||
|
|
0595c5545e | ||
|
|
55944257aa | ||
|
|
03241778fa | ||
|
|
555b81fb14 | ||
|
|
a56b720e09 | ||
|
|
b89eede164 | ||
|
|
c21cc8d6b9 | ||
|
|
d02a6bc197 | ||
|
|
360c1ce82d | ||
|
|
a7ef02976c | ||
|
|
6a9e36ea4d | ||
|
|
5ebca3ff06 | ||
|
|
37d4c0a40f | ||
|
|
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 | ||
|
|
0a68d2791d | ||
|
|
11928d9a7e | ||
|
|
c169bb5d5d | ||
|
|
3cc4f1c63e | ||
|
|
5237b1d535 | ||
|
|
cd56c50cf9 | ||
|
|
a18ce18d72 | ||
|
|
3691d32aaa | ||
|
|
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 | ||
|
|
7e741e4af9 | ||
|
|
24d47ae1c5 | ||
|
|
f556d59ad7 | ||
|
|
09c4662436 | ||
|
|
9bf6ba9cf0 | ||
|
|
7843ca9b1a | ||
|
|
c78b9866a3 | ||
|
|
c8701aba63 | ||
|
|
09c1a8ae35 | ||
|
|
0ef2814de3 | ||
|
|
8e105f0b36 | ||
|
|
ba4da3e35c | ||
|
|
1b8be56c15 | ||
|
|
f6e65f82e5 | ||
|
|
8b7bb099f3 | ||
|
|
2b26db78eb | ||
|
|
663d91b648 | ||
|
|
2a7686ec75 | ||
|
|
549cb56cdf | ||
|
|
146bb6c5c0 | ||
|
|
67b6da7c31 | ||
|
|
c2d96922c8 | ||
|
|
624894621b | ||
|
|
3fba215266 | ||
|
|
bbf291e8f3 | ||
|
|
70b4ec7948 | ||
|
|
341fc09c22 | ||
|
|
a3ec364034 | ||
|
|
fb30529808 | ||
|
|
e1728b275b | ||
|
|
46999145fc | ||
|
|
9d1f810af2 | ||
|
|
10d55df461 | ||
|
|
b9693aae95 | ||
|
|
02f5f1985c | ||
|
|
37edceee84 | ||
|
|
1fd63012b0 | ||
|
|
cf084fa168 | ||
|
|
96af289640 | ||
|
|
f8df351de6 | ||
|
|
b8c096f4ff | ||
|
|
93ad23b615 | ||
|
|
0a918535bb | ||
|
|
4863b16b5f | ||
|
|
a27f5e2153 | ||
|
|
3ffea50072 | ||
|
|
34af7d3880 | ||
|
|
4432721c27 | ||
|
|
65ad4d9426 | ||
|
|
18924b4f08 | ||
|
|
c524f5f0e0 | ||
|
|
2fbac78eec | ||
|
|
4734971d48 | ||
|
|
fc1055c644 | ||
|
|
24798390b5 | ||
|
|
a992dead04 | ||
|
|
f039cd8d0d | ||
|
|
494e691230 | ||
|
|
4cc7366290 | ||
|
|
fd9d660a61 | ||
|
|
0b5bd4f718 | ||
|
|
7511b42bd4 | ||
|
|
26f94c4d5b | ||
|
|
aa55f4840b | ||
|
|
2810428d19 | ||
|
|
83da64f96b | ||
|
|
1f8d027f97 | ||
|
|
2f8ba20a5b | ||
|
|
b525f920e0 | ||
|
|
91fe6745fe | ||
|
|
b9073fe3f5 | ||
|
|
2c95b49ae1 | ||
|
|
9bde06e110 | ||
|
|
30449ca113 | ||
|
|
0405d19f98 | ||
|
|
2c5310403b | ||
|
|
e2ef4f1caf | ||
|
|
b32a2d4d86 | ||
|
|
7c92484ae0 | ||
|
|
67cada5d8e | ||
|
|
4bf68b637f | ||
|
|
b40c433865 | ||
|
|
55ed499ab5 | ||
|
|
353bc3bc05 | ||
|
|
df5c96345c | ||
|
|
2c7c187c45 | ||
|
|
3a5a78d60a | ||
|
|
6dd2871c07 | ||
|
|
ef36ab9da0 | ||
|
|
a917f6bcdf | ||
|
|
c5d6457146 | ||
|
|
f3831e934f |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -121,3 +121,12 @@ logs/oAuthClient-log.log
|
||||
/*.env.*
|
||||
.idea/*
|
||||
.idea
|
||||
|
||||
# Vitest
|
||||
vitest-report*/
|
||||
vitest-coverage/
|
||||
*.vitest.log
|
||||
test-output.txt
|
||||
server/job/test/fixtures
|
||||
|
||||
.github
|
||||
|
||||
@@ -56,4 +56,5 @@ COPY . .
|
||||
EXPOSE 4000 9229
|
||||
|
||||
# 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",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"express": "^4.21.1",
|
||||
"mailparser": "^3.7.1",
|
||||
"express": "^5.1.0",
|
||||
"mailparser": "^3.7.2",
|
||||
"node-fetch": "^3.3.2"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,3 +12,5 @@ VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=IMEX
|
||||
TEST_USERNAME="test@imex.dev"
|
||||
TEST_PASSWORD="test123"
|
||||
|
||||
@@ -14,3 +14,5 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_COUNTRY=USA
|
||||
VITE_APP_INSTANCE=ROME
|
||||
TEST_USERNAME="test@imex.dev"
|
||||
TEST_PASSWORD="test123"
|
||||
|
||||
11
client/.gitignore
vendored
11
client/.gitignore
vendored
@@ -1,3 +1,14 @@
|
||||
# Vitest
|
||||
vitest-report*/
|
||||
vitest-coverage/
|
||||
*.vitest.log
|
||||
test-output.txt
|
||||
|
||||
# Playwright
|
||||
playwright-report/
|
||||
test-results/
|
||||
playwright/.cache/
|
||||
*.playwright.log
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
|
||||
@@ -74,50 +74,8 @@
|
||||
})();
|
||||
</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 || []);
|
||||
<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 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>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
3383
client/package-lock.json
generated
3383
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,24 +8,23 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@ant-design/pro-layout": "^7.22.3",
|
||||
"@apollo/client": "^3.13.5",
|
||||
"@ant-design/pro-layout": "^7.22.4",
|
||||
"@apollo/client": "^3.13.6",
|
||||
"@emotion/is-prop-valid": "^1.3.1",
|
||||
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
||||
"@firebase/analytics": "^0.10.12",
|
||||
"@firebase/app": "^0.11.3",
|
||||
"@firebase/auth": "^1.9.1",
|
||||
"@firebase/app": "^0.11.4",
|
||||
"@firebase/auth": "^1.10.0",
|
||||
"@firebase/firestore": "^4.7.10",
|
||||
"@firebase/messaging": "^0.12.17",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.6.1",
|
||||
"@sentry/cli": "^2.42.4",
|
||||
"@sentry/react": "^9.9.0",
|
||||
"@sentry/vite-plugin": "^3.2.2",
|
||||
"@splitsoftware/splitio-react": "^2.0.1",
|
||||
"@sentry/cli": "^2.43.0",
|
||||
"@sentry/react": "^9.11.0",
|
||||
"@sentry/vite-plugin": "^3.3.1",
|
||||
"@splitsoftware/splitio-react": "^2.1.1",
|
||||
"@tanem/react-nprogress": "^5.0.53",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"antd": "^5.24.5",
|
||||
"antd": "^5.24.6",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^4.2.0",
|
||||
"autosize": "^6.0.1",
|
||||
@@ -58,7 +57,7 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-grid-gallery": "^1.0.1",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
@@ -71,16 +70,16 @@
|
||||
"react-router-dom": "^6.30.0",
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-virtuoso": "^4.12.5",
|
||||
"recharts": "^2.15.0",
|
||||
"recharts": "^2.15.2",
|
||||
"redux": "^5.0.1",
|
||||
"redux-actions": "^3.0.3",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.1",
|
||||
"sass": "^1.86.0",
|
||||
"sass": "^1.86.3",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"styled-components": "^6.1.16",
|
||||
"styled-components": "^6.1.17",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"use-memo-one": "^1.1.3",
|
||||
"vite-plugin-ejs": "^1.7.0",
|
||||
@@ -100,7 +99,14 @@
|
||||
"build:production:imex": "env-cmd -f .env.production.imex npm run build",
|
||||
"build:production:rome": "env-cmd -f .env.production.rome npm run build",
|
||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
||||
"eulaize": "node src/utils/eulaize.js"
|
||||
"eulaize": "node src/utils/eulaize.js",
|
||||
"test:unit": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:e2e:imex": "playwright test --config playwright.config.js",
|
||||
"test:e2e:rome": "playwright test --config playwright.rome.config.js",
|
||||
"test:e2e:imex:headed": "playwright test --config playwright.config.js --headed",
|
||||
"test:e2e:rome:headed": "playwright test --config playwright.rome.config.js --headed",
|
||||
"test:e2e:report": "playwright show-report"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -124,29 +130,37 @@
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
"@dotenvx/dotenvx": "^1.39.0",
|
||||
"@dotenvx/dotenvx": "^1.39.1",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@eslint/js": "^9.23.0",
|
||||
"@sentry/webpack-plugin": "^3.2.2",
|
||||
"@eslint/js": "^9.24.0",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@sentry/webpack-plugin": "^3.3.1",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"browserslist": "^4.24.4",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"chalk": "^5.4.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^15.15.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"memfs": "^4.17.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
"playwright": "^1.51.1",
|
||||
"react-error-overlay": "^6.1.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^6.2.3",
|
||||
"vite": "^6.2.5",
|
||||
"vite-plugin-babel": "^1.3.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"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",
|
||||
"vitest": "^3.1.1",
|
||||
"workbox-window": "^7.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
25
client/playwright.config.js
Normal file
25
client/playwright.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineConfig } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config({
|
||||
path: "./.env.development.imex",
|
||||
prefix: "TEST_"
|
||||
});
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./tests/e2e",
|
||||
testMatch: "*.e2e.js",
|
||||
timeout: 60 * 1000,
|
||||
reporter: [["list"], ["html"]],
|
||||
use: {
|
||||
baseURL: "https://localhost:3000",
|
||||
browser: "chromium",
|
||||
ignoreHTTPSErrors: true
|
||||
},
|
||||
webServer: {
|
||||
command: "npm run start:imex",
|
||||
ignoreHTTPSErrors: true,
|
||||
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
||||
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
||||
}
|
||||
});
|
||||
25
client/playwright.rome.config.js
Normal file
25
client/playwright.rome.config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineConfig } from "@playwright/test";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config({
|
||||
path: "./.env.development.rome",
|
||||
prefix: "TEST_"
|
||||
});
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./tests/e2e",
|
||||
testMatch: "*.e2e.js",
|
||||
timeout: 60 * 1000,
|
||||
reporter: [["list"], ["html"]],
|
||||
use: {
|
||||
baseURL: "https://localhost:3000",
|
||||
browser: "chromium",
|
||||
ignoreHTTPSErrors: true
|
||||
},
|
||||
webServer: {
|
||||
command: "npm run start:rome",
|
||||
ignoreHTTPSErrors: true,
|
||||
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
||||
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
||||
}
|
||||
});
|
||||
@@ -10,6 +10,7 @@ import client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import themeProvider from "./themeProvider";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
|
||||
// Base Split configuration
|
||||
const config = {
|
||||
@@ -38,26 +39,28 @@ function AppContainer() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
input={{ autoComplete: "new-password" }}
|
||||
locale={enLocale}
|
||||
theme={themeProvider}
|
||||
form={{
|
||||
validateMessages: {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
required: t("general.validation.required", { label: "${label}" })
|
||||
}
|
||||
}}
|
||||
>
|
||||
<GlobalLoadingBar />
|
||||
<SplitFactoryProvider config={config}>
|
||||
<SplitClientProvider>
|
||||
<App />
|
||||
</SplitClientProvider>
|
||||
</SplitFactoryProvider>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
<CookiesProvider>
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
input={{ autoComplete: "new-password" }}
|
||||
locale={enLocale}
|
||||
theme={themeProvider}
|
||||
form={{
|
||||
validateMessages: {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
required: t("general.validation.required", { label: "${label}" })
|
||||
}
|
||||
}}
|
||||
>
|
||||
<GlobalLoadingBar />
|
||||
<SplitFactoryProvider config={config}>
|
||||
<SplitClientProvider>
|
||||
<App />
|
||||
</SplitClientProvider>
|
||||
</SplitFactoryProvider>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
</CookiesProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ import "./App.styles.scss";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||
import { SocketProvider } from "../contexts/SocketIO/useSocket.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 ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||
@@ -142,11 +142,10 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
>
|
||||
<ProductFruitsWrapper
|
||||
currentUser={currentUser}
|
||||
workspaceCode={InstanceRenderMgr({
|
||||
imex: null,
|
||||
rome: "9BkbEseqNqxw8jUH"
|
||||
})}
|
||||
bodyshop={bodyshop}
|
||||
workspaceCode={bodyshop?.tours_enabled ? "9BkbEseqNqxw8jUH" : ""}
|
||||
/>
|
||||
|
||||
<NotificationProvider>
|
||||
<Routes>
|
||||
<Route
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import React from "react";
|
||||
import { ProductFruits } from "react-product-fruits";
|
||||
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 (
|
||||
workspaceCode &&
|
||||
currentUser?.authorized === true &&
|
||||
@@ -14,7 +22,8 @@ const ProductFruitsWrapper = React.memo(({ currentUser, workspaceCode }) => {
|
||||
language="en"
|
||||
user={{
|
||||
email: currentUser.email,
|
||||
username: currentUser.email
|
||||
username: currentUser.email,
|
||||
props: featureProps
|
||||
}}
|
||||
/>
|
||||
)
|
||||
@@ -28,5 +37,6 @@ ProductFruitsWrapper.propTypes = {
|
||||
authorized: PropTypes.bool,
|
||||
email: PropTypes.string
|
||||
}),
|
||||
workspaceCode: PropTypes.string
|
||||
workspaceCode: PropTypes.string,
|
||||
bodyshop: PropTypes.object
|
||||
};
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Alert component should render Alert component 1`] = `ShallowWrapper {}`;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import Alert from "./alert.component";
|
||||
|
||||
describe("Alert component", () => {
|
||||
let wrapper;
|
||||
beforeEach(() => {
|
||||
const mockProps = {
|
||||
type: "error",
|
||||
message: "Test error message."
|
||||
};
|
||||
|
||||
wrapper = shallow(<Alert {...mockProps} />);
|
||||
});
|
||||
|
||||
it("should render Alert component", () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
31
client/src/components/alert/alert.component.test.jsx
Normal file
31
client/src/components/alert/alert.component.test.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import AlertComponent from "./alert.component";
|
||||
|
||||
describe("AlertComponent", () => {
|
||||
it("renders with default props", () => {
|
||||
render(<AlertComponent message="Default Alert" />);
|
||||
expect(screen.getByText("Default Alert")).toBeInTheDocument();
|
||||
expect(screen.getByRole("alert")).toHaveClass("ant-alert");
|
||||
});
|
||||
|
||||
it("applies type prop correctly", () => {
|
||||
render(<AlertComponent message="Success Alert" type="success" />);
|
||||
const alert = screen.getByRole("alert");
|
||||
expect(screen.getByText("Success Alert")).toBeInTheDocument();
|
||||
expect(alert).toHaveClass("ant-alert-success");
|
||||
});
|
||||
|
||||
it("displays description when provided", () => {
|
||||
render(<AlertComponent message="Error Alert" description="Something went wrong" type="error" />);
|
||||
expect(screen.getByText("Error Alert")).toBeInTheDocument();
|
||||
expect(screen.getByText("Something went wrong")).toBeInTheDocument();
|
||||
expect(screen.getByRole("alert")).toHaveClass("ant-alert-error");
|
||||
});
|
||||
|
||||
it("is closable and shows icon when props are set", () => {
|
||||
render(<AlertComponent message="Warning Alert" type="warning" showIcon closable />);
|
||||
expect(screen.getByText("Warning Alert")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /close/i })).toBeInTheDocument(); // Close button
|
||||
});
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AllocationsAssignmentComponent component should create an allocation on save 1`] = `ReactWrapper {}`;
|
||||
|
||||
exports[`AllocationsAssignmentComponent component should render AllocationsAssignmentComponent component 1`] = `ReactWrapper {}`;
|
||||
@@ -1,35 +0,0 @@
|
||||
import { mount } from "enzyme";
|
||||
import React from "react";
|
||||
import { MockBodyshop } from "../../utils/TestingHelpers";
|
||||
import { AllocationsAssignmentComponent } from "./allocations-assignment.component";
|
||||
|
||||
describe("AllocationsAssignmentComponent component", () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockProps = {
|
||||
bodyshop: MockBodyshop,
|
||||
handleAssignment: jest.fn(),
|
||||
assignment: {},
|
||||
setAssignment: jest.fn(),
|
||||
visibilityState: [false, jest.fn()],
|
||||
maxHours: 4
|
||||
};
|
||||
|
||||
wrapper = mount(<AllocationsAssignmentComponent {...mockProps} />);
|
||||
});
|
||||
|
||||
it("should render AllocationsAssignmentComponent component", () => {
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render a list of employees", () => {
|
||||
const empList = wrapper.find("#employeeSelector");
|
||||
expect(empList.children()).to.have.lengthOf(2);
|
||||
});
|
||||
|
||||
it("should create an allocation on save", () => {
|
||||
wrapper.find("Button").simulate("click");
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -3,11 +3,11 @@ import { getToken } from "@firebase/messaging";
|
||||
import axios from "axios";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { messaging, requestForToken } from "../../firebase/firebase.utils";
|
||||
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
||||
import "./chat-affix.styles.scss";
|
||||
import { registerMessagingHandlers, unregisterMessagingHandlers } from "./registerMessagingSocketHandlers";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -3,10 +3,10 @@ import { Button } from "antd";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
import { connect } from "react-redux";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
|
||||
@@ -4,10 +4,10 @@ import { Link } from "react-router-dom";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
import { connect } from "react-redux";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
|
||||
@@ -3,11 +3,11 @@ import axios from "axios";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { CONVERSATION_SUBSCRIPTION_BY_PK, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
||||
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ChatConversationComponent from "./chat-conversation.component";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedConversation: selectSelectedConversation,
|
||||
|
||||
@@ -4,11 +4,11 @@ import { Input, Spin, Tag, Tooltip } from "antd";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
import { connect } from "react-redux";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
|
||||
@@ -5,7 +5,7 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { openChatByPhone } from "../../redux/messaging/messaging.actions";
|
||||
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({
|
||||
//currentUser: selectCurrentUser
|
||||
|
||||
@@ -7,7 +7,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.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";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
|
||||
@@ -12,9 +12,9 @@ import ChatConversationListComponent from "../chat-conversation-list/chat-conver
|
||||
import ChatConversationContainer from "../chat-conversation/chat-conversation.container";
|
||||
import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
|
||||
import "./chat-popup.styles.scss";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedConversation: selectSelectedConversation,
|
||||
|
||||
@@ -8,10 +8,10 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
||||
import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries";
|
||||
import ChatTagRo from "./chat-tag-ro.component";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
import { connect } from "react-redux";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
|
||||
@@ -20,6 +20,7 @@ function FeatureWrapper({
|
||||
children,
|
||||
upsellComponent,
|
||||
bypass,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
...restProps
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
@@ -78,7 +79,11 @@ export function HasFeatureAccess({ featureName, bodyshop, bypass, debug = false
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return bodyshop?.features?.allAccess || dayjs(bodyshop?.features[featureName]).isAfter(dayjs());
|
||||
return (
|
||||
bodyshop?.features?.allAccess ||
|
||||
bodyshop?.features?.[featureName] ||
|
||||
dayjs(bodyshop?.features[featureName]).isAfter(dayjs())
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(FeatureWrapper);
|
||||
|
||||
@@ -40,7 +40,6 @@ import { RiSurveyLine } from "react-icons/ri";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js";
|
||||
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
@@ -51,6 +50,7 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import LockWrapper from "../lock-wrapper/lock-wrapper.component";
|
||||
import NotificationCenterContainer from "../notification-center/notification-center.container.jsx";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
// Redux mappings
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 parsePhoneNumber from "libphonenumber-js";
|
||||
import queryString from "query-string";
|
||||
@@ -8,24 +8,30 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
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 { 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 { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateTimeFormatterFunction } from "../../utils/DateFormatter";
|
||||
import dayjs from "../../utils/day";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.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 ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.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 ScheduleEventColor from "./schedule-event.color.component";
|
||||
import ScheduleEventNote from "./schedule-event.note.component";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -33,7 +39,8 @@ const mapStateToProps = createStructuredSelector({
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
|
||||
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({
|
||||
@@ -43,16 +50,41 @@ export function ScheduleEventComponent({
|
||||
event,
|
||||
refetch,
|
||||
handleCancel,
|
||||
setScheduleContext
|
||||
setScheduleContext,
|
||||
insertAuditTrail
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const history = useNavigate();
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
const [mutationUpdateJob] = useMutation(JOB_PRODUCTION_TOGGLE);
|
||||
const [title, setTitle] = useState(event.title);
|
||||
const { socket } = useSocket();
|
||||
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 = (
|
||||
<Space direction="vertical" wrap>
|
||||
@@ -89,6 +121,74 @@ export function ScheduleEventComponent({
|
||||
</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 = (
|
||||
<div style={{ maxWidth: "40vw" }}>
|
||||
{!event.isintake ? (
|
||||
@@ -294,7 +394,7 @@ export function ScheduleEventComponent({
|
||||
) : (
|
||||
<ScheduleManualEvent event={event} />
|
||||
)}
|
||||
{event.isintake ? (
|
||||
{event.isintake && HasFeatureAccess({ featureName: "checklist", bodyshop }) ? (
|
||||
<Link
|
||||
to={{
|
||||
pathname: `/manage/jobs/${event.job && event.job.id}/intake`,
|
||||
@@ -303,7 +403,21 @@ export function ScheduleEventComponent({
|
||||
>
|
||||
<Button disabled={event.arrived}>{t("appointments.actions.intake")}</Button>
|
||||
</Link>
|
||||
) : null}
|
||||
) : (
|
||||
<Popover //open={open}
|
||||
content={popMenu}
|
||||
open={popOverVisible}
|
||||
onOpenChange={setPopOverVisible}
|
||||
onClick={(e) => {
|
||||
getJobDetails();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
getPopupContainer={(trigger) => trigger.parentNode}
|
||||
trigger="click"
|
||||
>
|
||||
<Button disabled={event.arrived}>{t("jobs.actions.intake_quick")}</Button>
|
||||
</Popover>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,8 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
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 { QUERY_JOB_CARD_DETAILS, UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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 JobReconciliationPartsTable from "../job-reconciliation-parts-table/job-reconciliation-parts-table.component";
|
||||
import JobReconciliationTotals from "../job-reconciliation-totals/job-reconciliation-totals.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
export default function JobReconciliationModalComponent({ job, bills }) {
|
||||
const jobLineState = useState([]);
|
||||
@@ -20,7 +20,7 @@ export default function JobReconciliationModalComponent({ job, bills }) {
|
||||
|
||||
const filterFunction = InstanceRenderManager({
|
||||
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.db_ref === "936004", //ADD SHIPPING LINE.
|
||||
rome: (j) =>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
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">
|
||||
<Switch disabled={jobRO} />
|
||||
</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>
|
||||
</Col>
|
||||
<Col {...lossColDamage}>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
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 { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
|
||||
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
|
||||
@@ -133,6 +133,16 @@ export function JobsDetailHeaderActions({
|
||||
const { socket } = useSocket();
|
||||
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 {
|
||||
treatments: { ImEXPay }
|
||||
} = useSplitTreatments({
|
||||
@@ -171,7 +181,7 @@ export function JobsDetailHeaderActions({
|
||||
{ defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
|
||||
(newJobId) => {
|
||||
history(`/manage/jobs/${newJobId}`);
|
||||
notification["success"]({
|
||||
notification.success({
|
||||
message: t("jobs.successes.duplicated")
|
||||
});
|
||||
},
|
||||
@@ -181,7 +191,7 @@ export function JobsDetailHeaderActions({
|
||||
const handleDuplicateConfirm = () =>
|
||||
DuplicateJob(client, job.id, { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, (newJobId) => {
|
||||
history(`/manage/jobs/${newJobId}`);
|
||||
notification["success"]({
|
||||
notification.success({
|
||||
message: t("jobs.successes.duplicated")
|
||||
});
|
||||
});
|
||||
@@ -217,13 +227,13 @@ export function JobsDetailHeaderActions({
|
||||
const result = await deleteJob({ variables: { id: job.id } });
|
||||
|
||||
if (!result.errors) {
|
||||
notification["success"]({
|
||||
notification.success({
|
||||
message: t("jobs.successes.delete")
|
||||
});
|
||||
//go back to jobs list.
|
||||
history(`/manage/`);
|
||||
} else {
|
||||
notification["error"]({
|
||||
notification.error({
|
||||
message: t("jobs.errors.deleted", {
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
@@ -275,9 +285,9 @@ export function JobsDetailHeaderActions({
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
notification["success"]({ message: t("csi.successes.created") });
|
||||
notification.success({ message: t("csi.successes.created") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
notification.error({
|
||||
message: t("csi.errors.creating", {
|
||||
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}`
|
||||
);
|
||||
} else {
|
||||
notification["error"]({
|
||||
notification.error({
|
||||
message: t("messaging.error.invalidphone")
|
||||
});
|
||||
}
|
||||
@@ -328,7 +338,7 @@ export function JobsDetailHeaderActions({
|
||||
);
|
||||
}
|
||||
} else {
|
||||
notification["error"]({
|
||||
notification.error({
|
||||
message: t("csi.errors.notconfigured")
|
||||
});
|
||||
}
|
||||
@@ -358,7 +368,7 @@ export function JobsDetailHeaderActions({
|
||||
});
|
||||
setMessage(`${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}`);
|
||||
} else {
|
||||
notification["error"]({
|
||||
notification.error({
|
||||
message: t("messaging.error.invalidphone")
|
||||
});
|
||||
}
|
||||
@@ -398,7 +408,7 @@ export function JobsDetailHeaderActions({
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
notification["success"]({
|
||||
notification.success({
|
||||
message: t("jobs.successes.voided")
|
||||
});
|
||||
insertAuditTrail({
|
||||
@@ -409,7 +419,7 @@ export function JobsDetailHeaderActions({
|
||||
//go back to jobs list.
|
||||
history(`/manage/`);
|
||||
} else {
|
||||
notification["error"]({
|
||||
notification.error({
|
||||
message: t("jobs.errors.voiding", {
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
@@ -442,7 +452,7 @@ export function JobsDetailHeaderActions({
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
notification.error({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message)
|
||||
})
|
||||
@@ -460,7 +470,7 @@ export function JobsDetailHeaderActions({
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
notification.error({
|
||||
message: t("jobs.errors.exporting-partner")
|
||||
});
|
||||
|
||||
@@ -556,7 +566,7 @@ export function JobsDetailHeaderActions({
|
||||
}
|
||||
});
|
||||
if (!jobUpdate.errors) {
|
||||
notification["success"]({
|
||||
notification.success({
|
||||
message: t("appointments.successes.canceled")
|
||||
});
|
||||
insertAuditTrail({
|
||||
@@ -931,11 +941,11 @@ export function JobsDetailHeaderActions({
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
notification["success"]({
|
||||
notification.success({
|
||||
message: t("jobs.successes.partsqueue")
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
notification.error({
|
||||
message: t("jobs.errors.saving", {
|
||||
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 = {
|
||||
items: menuItems,
|
||||
key: "popovermenu"
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
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 { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
@@ -12,7 +13,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { DateTimeFormatterFunction } from "../../utils/DateFormatter";
|
||||
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";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -44,9 +44,16 @@ export function JobsDetailHeaderActionsToggleProduction({
|
||||
variables: { id: 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,
|
||||
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,
|
||||
scheduled_delivery: data.jobs_by_pk.scheduled_delivery,
|
||||
actual_delivery: data.jobs_by_pk.actual_delivery
|
||||
|
||||
@@ -10,6 +10,7 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import dayjs from "../../utils/day";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
@@ -21,7 +22,6 @@ import ProductionListColumnComment from "../production-list-columns/production-l
|
||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
import "./jobs-detail-header.styles.scss";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -149,6 +149,14 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
</Space>
|
||||
</Tag>
|
||||
)}
|
||||
{job.hit_and_run && (
|
||||
<Tag color="green">
|
||||
<Space>
|
||||
<WarningFilled />
|
||||
<span>{t("jobs.fields.hit_and_run")}</span>
|
||||
</Space>
|
||||
</Tag>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Button, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import cleanAxios from "../../utils/CleanAxios";
|
||||
import formatBytes from "../../utils/formatbytes";
|
||||
//import yauzl from "yauzl";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
@@ -28,7 +26,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyDownloadButton);
|
||||
|
||||
export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, identifier }) {
|
||||
export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, identifier, jobId }) {
|
||||
const { t } = useTranslation();
|
||||
const [download, setDownload] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -46,6 +44,7 @@ export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, i
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function standardMediaDownload(bufferData) {
|
||||
const a = document.createElement("a");
|
||||
const url = window.URL.createObjectURL(new Blob([bufferData]));
|
||||
@@ -53,13 +52,14 @@ export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, i
|
||||
a.download = `${identifier || "documents"}.zip`;
|
||||
a.click();
|
||||
}
|
||||
|
||||
const handleDownload = async () => {
|
||||
logImEXEvent("jobs_documents_download");
|
||||
setLoading(true);
|
||||
const zipUrl = await axios({
|
||||
url: "/media/imgproxy/download",
|
||||
method: "POST",
|
||||
data: { documentids: imagesToDownload.map((_) => _.id) }
|
||||
data: { jobId, documentids: imagesToDownload.map((_) => _.id) }
|
||||
});
|
||||
|
||||
const theDownloadedZip = await cleanAxios({
|
||||
|
||||
@@ -75,7 +75,7 @@ function JobsDocumentsImgproxyComponent({
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<JobsDocumentsGallerySelectAllComponent galleryImages={galleryImages} setGalleryImages={setGalleryImages} />
|
||||
<JobsDocumentsDownloadButton galleryImages={galleryImages} identifier={downloadIdentifier} />
|
||||
<JobsDocumentsDownloadButton galleryImages={galleryImages} identifier={downloadIdentifier} jobId={jobId} />
|
||||
<JobsDocumentsDeleteButton
|
||||
galleryImages={galleryImages}
|
||||
deletionCallback={billsCallback || fetchThumbnails || refetch}
|
||||
|
||||
@@ -3,10 +3,10 @@ import { useQuery } from "@apollo/client";
|
||||
import { connect } from "react-redux";
|
||||
import NotificationCenterComponent from "./notification-center.component";
|
||||
import { GET_NOTIFICATIONS } from "../../graphql/notifications.queries";
|
||||
import { INITIAL_NOTIFICATIONS, useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
import day from "../../utils/day.js";
|
||||
import { INITIAL_NOTIFICATIONS, useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
// This will be used to poll for notifications when the socket is disconnected
|
||||
const NOTIFICATION_POLL_INTERVAL_SECONDS = 60;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
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({
|
||||
bodyshop: selectBodyshop,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from "react";
|
||||
import { Card, Form, Select } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const FilterSettings = ({
|
||||
selectedMdInsCos,
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Card, Checkbox, Col, Form, Row } from "antd";
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const InformationSettings = ({ t }) => (
|
||||
<Card title={t("production.settings.information")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Card title={t("production.settings.information")} style={{ maxWidth: "100%", overflowX: "auto" }}>
|
||||
<Row gutter={[16, 16]} wrap>
|
||||
{[
|
||||
"model_info",
|
||||
"ownr_nm",
|
||||
@@ -21,7 +20,7 @@ const InformationSettings = ({ t }) => (
|
||||
"subtotal",
|
||||
"tasks"
|
||||
].map((item) => (
|
||||
<Col span={4} key={item}>
|
||||
<Col xs={24} sm={12} md={8} lg={6} key={item}>
|
||||
<Form.Item name={item} valuePropName="checked">
|
||||
<Checkbox>{t(`production.labels.${item}`)}</Checkbox>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Card, Col, Form, Radio, Row } from "antd";
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const LayoutSettings = ({ t }) => (
|
||||
<Card title={t("production.settings.layout")}>
|
||||
<Card title={t("production.settings.layout")} style={{ maxWidth: "100%", overflowX: "auto" }}>
|
||||
<Row gutter={[16, 16]}>
|
||||
{[
|
||||
{
|
||||
@@ -48,9 +47,9 @@ const LayoutSettings = ({ t }) => (
|
||||
]
|
||||
}
|
||||
].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}>
|
||||
<Radio.Group>
|
||||
<Radio.Group style={{ display: "flex", flexWrap: "nowrap" }}>
|
||||
{options.map((option) => (
|
||||
<Radio.Button key={option.value.toString()} value={option.value}>
|
||||
{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 { 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 onDragEnd = (result) => {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { SettingOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
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 { useTranslation } from "react-i18next";
|
||||
import { useNotification } from "../../../contexts/Notifications/notificationContext.jsx";
|
||||
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.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 PropTypes from "prop-types";
|
||||
import { isFunction } from "lodash";
|
||||
import { useNotification } from "../../../contexts/Notifications/notificationContext.jsx";
|
||||
import { SettingOutlined } from "@ant-design/icons";
|
||||
import InformationSettings from "./InformationSettings.jsx";
|
||||
import LayoutSettings from "./LayoutSettings.jsx";
|
||||
import StatisticsSettings from "./StatisticsSettings.jsx";
|
||||
|
||||
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) {
|
||||
const [form] = Form.useForm();
|
||||
@@ -87,7 +87,7 @@ function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bod
|
||||
};
|
||||
|
||||
const overlay = (
|
||||
<Card style={{ minWidth: "80vw" }}>
|
||||
<Card style={{ maxWidth: "80vw", width: "100%"}}>
|
||||
<Form form={form} onFinish={handleFinish} layout="vertical" onValuesChange={handleValuesChange}>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
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 { QUERY_JOB_CARD_DETAILS, UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import ProductionListTable from "./production-list-table.component";
|
||||
import _ from "lodash";
|
||||
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" }) {
|
||||
const client = useApolloClient();
|
||||
|
||||
@@ -8,7 +8,7 @@ import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { logImEXEvent, updateCurrentPassword } from "../../firebase/firebase.utils";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
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";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
|
||||
@@ -54,6 +54,9 @@ export function ProfileShopsContainer({ bodyshop, currentUser }) {
|
||||
|
||||
//Force window refresh.
|
||||
|
||||
//Ping the new partner to refresh.
|
||||
axios.post("http://localhost:1337/refresh");
|
||||
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Button, Col, Form, Input, Row, Select, Space, Switch, Typography } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -8,16 +8,16 @@ import { calculateScheduleLoad } from "../../redux/application/application.actio
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import dayjs from "../../utils/day";
|
||||
import BlurWrapper from "../feature-wrapper/blur-wrapper.component";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import EmailInput from "../form-items-formatted/email-form-item.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 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 "./schedule-job-modal.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -60,10 +60,12 @@ export function ScheduleJobModalComponent({
|
||||
const totalHours =
|
||||
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)
|
||||
form.setFieldsValue({
|
||||
scheduled_completion: dayjs(values.start).businessDaysAdd(totalHours / bodyshop.target_touchtime, "day")
|
||||
});
|
||||
if (values.start && !values.scheduled_completion) {
|
||||
const addDays = bodyshop.ss_configuration.nobusinessdays
|
||||
? 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 });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -906,6 +906,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
||||
add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
id="insurancecos-add-button"
|
||||
>
|
||||
{t("general.actions.add")}
|
||||
</Button>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { Button, Divider, Form, Input, InputNumber, Select, Space, Switch, TimePicker } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import { 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({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
@@ -78,6 +77,13 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["ss_configuration", "nobusinessdays"]}
|
||||
label={t("bodyshop.fields.ss_configuration.nobusinessdays")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_lost_sale_reasons"]}
|
||||
label={t("bodyshop.fields.md_lost_sale_reasons")}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useLazyQuery } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { Card, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
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 { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
||||
import JobEmployeeAssignmentsContainer from "./../job-employee-assignments/job-employee-assignments.container";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -319,10 +319,15 @@ export function TimeTicketModalComponent({
|
||||
}
|
||||
|
||||
export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideTimeTickets = false }) {
|
||||
const { t } = useTranslation();
|
||||
if (loading) return <LoadingSkeleton />;
|
||||
if (!lineTicketData) return null;
|
||||
if (!jobid) return null;
|
||||
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
|
||||
jobId={jobid}
|
||||
joblines={lineTicketData.joblines}
|
||||
@@ -332,6 +337,6 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideT
|
||||
{!hideTimeTickets && (
|
||||
<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 { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Form, Modal, Space } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
|
||||
import { INSERT_NEW_TIME_TICKET, UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
@@ -14,7 +15,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import dayjs from "../../utils/day";
|
||||
import TimeTicketsCommitToggleComponent from "../time-tickets-commit-toggle/time-tickets-commit-toggle.component";
|
||||
import TimeTicketModalComponent from "./time-ticket-modal.component";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
timeTicketModal: selectTimeTicket,
|
||||
@@ -81,7 +81,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
|
||||
}
|
||||
};
|
||||
|
||||
const handleMutationSuccess = (response) => {
|
||||
const handleMutationSuccess = () => {
|
||||
notification["success"]({
|
||||
message: t("timetickets.successes.created")
|
||||
});
|
||||
@@ -123,7 +123,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
|
||||
if (timeTicketModal.open) form.resetFields();
|
||||
}, [timeTicketModal.open, form]);
|
||||
|
||||
const handleFieldsChange = (changedFields, allFields) => {
|
||||
const handleFieldsChange = (changedFields) => {
|
||||
if (!!changedFields.employeeid && !!EmployeeAutoCompleteData) {
|
||||
const emps = EmployeeAutoCompleteData.employees.filter((e) => e.id === changedFields.employeeid);
|
||||
form.setFieldsValue({
|
||||
@@ -182,6 +182,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
|
||||
</Space>
|
||||
}
|
||||
destroyOnClose
|
||||
id="time-ticket-modal"
|
||||
>
|
||||
<Form
|
||||
onFinish={handleFinish}
|
||||
|
||||
@@ -113,7 +113,7 @@ export function UpdateAlert({ updateAvailable }) {
|
||||
</Col>
|
||||
<Col sm={24} md={8} lg={6}>
|
||||
<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")}
|
||||
</Button>
|
||||
<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 { auth } from "../../firebase/firebase.utils";
|
||||
import { store } from "../../redux/store";
|
||||
@@ -15,10 +16,7 @@ import {
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const SocketContext = createContext(null);
|
||||
|
||||
const INITIAL_NOTIFICATIONS = 10;
|
||||
import { SocketContext, INITIAL_NOTIFICATIONS } from "./useSocket.js";
|
||||
|
||||
/**
|
||||
* Socket Provider - Scenario Notifications / Web Socket related items
|
||||
@@ -216,7 +214,6 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
};
|
||||
|
||||
const handleNotification = (data) => {
|
||||
// Scenario Notifications have been disabled, bail.
|
||||
if (Realtime_Notifications_UI?.treatment !== "on") {
|
||||
return;
|
||||
}
|
||||
@@ -336,7 +333,6 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
};
|
||||
|
||||
const handleSyncNotificationRead = ({ notificationId, timestamp }) => {
|
||||
// Scenario Notifications have been disabled, bail.
|
||||
if (Realtime_Notifications_UI?.treatment !== "on") {
|
||||
return;
|
||||
}
|
||||
@@ -378,7 +374,6 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
};
|
||||
|
||||
const handleSyncAllNotificationsRead = ({ timestamp }) => {
|
||||
// Scenario Notifications have been disabled, bail.
|
||||
if (Realtime_Notifications_UI?.treatment !== "on") {
|
||||
return;
|
||||
}
|
||||
@@ -490,11 +485,4 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const useSocket = () => {
|
||||
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 };
|
||||
export default SocketProvider;
|
||||
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
|
||||
md_ro_statuses
|
||||
md_order_statuses
|
||||
tours_enabled
|
||||
md_functionality_toggles
|
||||
shopname
|
||||
state
|
||||
@@ -186,6 +187,7 @@ export const UPDATE_SHOP = gql`
|
||||
phone
|
||||
federal_tax_id
|
||||
id
|
||||
tours_enabled
|
||||
insurance_vendor_id
|
||||
logo_img_path
|
||||
md_ro_statuses
|
||||
|
||||
@@ -35,6 +35,30 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
lbr_adjustments
|
||||
converted
|
||||
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 } }) {
|
||||
id
|
||||
|
||||
@@ -423,6 +423,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
actual_completion
|
||||
actual_delivery
|
||||
actual_in
|
||||
acv_amount
|
||||
adjustment_bottom_line
|
||||
alt_transport
|
||||
area_of_damage
|
||||
@@ -511,6 +512,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
est_ph1
|
||||
flat_rate_ats
|
||||
federal_tax_rate
|
||||
hit_and_run
|
||||
id
|
||||
inproduction
|
||||
ins_addr1
|
||||
@@ -2570,6 +2572,20 @@ export const GET_JOB_BY_PK_QUICK_INTAKE = gql`
|
||||
actual_completion
|
||||
scheduled_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
|
||||
clm_no
|
||||
cieca_stl
|
||||
cieca_ttl
|
||||
cieca_pfl
|
||||
asgn_no
|
||||
asgn_date
|
||||
state_tax_rate
|
||||
@@ -164,6 +167,7 @@ export const QUERY_PARTS_ORDER_OEC = gql`
|
||||
loss_desc
|
||||
loss_of_use
|
||||
loss_type
|
||||
materials
|
||||
ownr_addr1
|
||||
ownr_addr2
|
||||
ownr_city
|
||||
|
||||
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 UndefinedToNull from "../../utils/undefinedtonull";
|
||||
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";
|
||||
|
||||
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 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 PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
||||
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 UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
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 { addAlerts } from "../../redux/application/application.actions.js";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
|
||||
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 ContractsList = lazy(() => import("../contracts/contracts.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 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]);
|
||||
|
||||
useEffect(() => {
|
||||
const widgetId = InstanceRenderManager({
|
||||
imex: "IABVNO4scRKY11XBQkNr",
|
||||
rome: "mQdqARMzkZRUVugJ6TdS"
|
||||
});
|
||||
window.noticeable.render("widget", widgetId);
|
||||
requestForToken().catch((error) => {
|
||||
console.error(`Unable to request for token.`, error);
|
||||
window.Canny("initChangelog", {
|
||||
appID: "680bd2c7ee501290377f6686",
|
||||
position: "top",
|
||||
align: "left",
|
||||
theme: "light" // options: light [default], dark, auto
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -480,6 +478,8 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
|
||||
// element={<ShopTemplates />}
|
||||
// />
|
||||
}
|
||||
<Route path="/feature-request/*" index element={<FeatureRequestPage />} />
|
||||
|
||||
<Route
|
||||
path="/shop/vendors"
|
||||
element={
|
||||
@@ -669,7 +669,12 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
|
||||
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 />
|
||||
<div onClick={broadcastMessage}>
|
||||
{`${InstanceRenderManager({
|
||||
@@ -677,8 +682,10 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
|
||||
rome: t("titles.romeonline")
|
||||
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
|
||||
</div>
|
||||
<div id="noticeable-widget" style={{ marginLeft: "1rem" }} />
|
||||
</div>
|
||||
<Button icon={<AlertOutlined />} data-canny-changelog type="text">
|
||||
{t("general.labels.changelog")}
|
||||
</Button>
|
||||
</Space>
|
||||
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
|
||||
Disclaimer & Notices
|
||||
</Link>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
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 {
|
||||
checkActionCode,
|
||||
@@ -12,6 +9,9 @@ import {
|
||||
} from "@firebase/auth";
|
||||
import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "@firebase/firestore";
|
||||
import { getToken } from "@firebase/messaging";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import { notification } from "antd";
|
||||
import axios from "axios";
|
||||
import i18next from "i18next";
|
||||
import LogRocket from "logrocket";
|
||||
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
|
||||
@@ -340,6 +340,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
args: [],
|
||||
imex: () => {
|
||||
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
||||
window.$crisp.push(["set", "session:segments", [[`region:${payload.region_config}`]]]);
|
||||
if (authRecord[0] && authRecord[0].user.validemail) {
|
||||
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
||||
}
|
||||
@@ -350,7 +351,14 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
});
|
||||
payload.features?.allAccess === true
|
||||
? 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) {
|
||||
console.warn("Couldnt find $crisp.", error.message);
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import { configure } from "enzyme";
|
||||
import Adapter from "enzyme-adapter-react-16";
|
||||
|
||||
configure({ adapter: new Adapter() });
|
||||
@@ -335,7 +335,6 @@
|
||||
"intellipay_config": {
|
||||
"cash_discount_percentage": "Cash Discount %",
|
||||
"enable_cash_discount": "Enable Cash Discounting",
|
||||
"payment_type": "Payment Type Map",
|
||||
"payment_map": {
|
||||
"amex": "American Express",
|
||||
"disc": "Discover",
|
||||
@@ -344,7 +343,8 @@
|
||||
"jcb": "JCB",
|
||||
"mast": "MasterCard",
|
||||
"visa": "Visa"
|
||||
}
|
||||
},
|
||||
"payment_type": "Payment Type Map"
|
||||
},
|
||||
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
|
||||
"invoice_local_tax_rate": "Invoices - Local Tax Rate",
|
||||
@@ -601,7 +601,8 @@
|
||||
"templates": "Templates"
|
||||
},
|
||||
"ss_configuration": {
|
||||
"dailyhrslimit": "Daily Incoming Hours Limit"
|
||||
"dailyhrslimit": "Daily Incoming Hours Limit",
|
||||
"nobusinessdays": "Include Weekends"
|
||||
},
|
||||
"ssbuckets": {
|
||||
"color": "Job Color",
|
||||
@@ -1220,7 +1221,8 @@
|
||||
"errors": {
|
||||
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
||||
"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": {
|
||||
"contract": "CC Contract",
|
||||
@@ -1234,6 +1236,7 @@
|
||||
"areyousure": "Are you sure?",
|
||||
"barcode": "Barcode",
|
||||
"cancel": "Are you sure you want to cancel? Your changes will not be saved.",
|
||||
"changelog": "Change Log",
|
||||
"clear": "Clear",
|
||||
"confirmpassword": "Confirm Password",
|
||||
"created_at": "Created At",
|
||||
@@ -1243,6 +1246,7 @@
|
||||
"errors": "Errors",
|
||||
"excel": "Excel",
|
||||
"exceptiontitle": "An error has occurred.",
|
||||
"feature-request": "Have a feature request?",
|
||||
"friday": "Friday",
|
||||
"globalsearch": "Global Search",
|
||||
"help": "Help",
|
||||
@@ -1321,6 +1325,7 @@
|
||||
"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.",
|
||||
"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.",
|
||||
"unsavedchangespopup": "You have unsaved changes. Are you sure you want to leave?"
|
||||
},
|
||||
@@ -1633,6 +1638,7 @@
|
||||
"actual_completion": "Actual Completion",
|
||||
"actual_delivery": "Actual Delivery",
|
||||
"actual_in": "Actual In",
|
||||
"acv_amount": "ACV Amount",
|
||||
"adjustment_bottom_line": "Adjustments",
|
||||
"adjustmenthours": "Adjustment Hours",
|
||||
"alt_transport": "Alt. Trans.",
|
||||
@@ -1758,9 +1764,10 @@
|
||||
"est_ct_ln": "Estimator Last Name",
|
||||
"est_ea": "Estimator Email",
|
||||
"est_ph1": "Estimator Phone #",
|
||||
"flat_rate_ats": "Flat Rate ATS?",
|
||||
"federal_tax_payable": "Federal Tax Payable",
|
||||
"federal_tax_rate": "Federal Tax Rate",
|
||||
"flat_rate_ats": "Flat Rate ATS?",
|
||||
"hit_and_run": "Hit and Run",
|
||||
"ins_addr1": "Insurance Co. Address",
|
||||
"ins_city": "Insurance Co. City",
|
||||
"ins_co_id": "Insurance Co. ID",
|
||||
@@ -2314,6 +2321,7 @@
|
||||
"duplicate": "Duplicate this Job",
|
||||
"duplicatenolines": "Duplicate this Job without Repair Data",
|
||||
"newcccontract": "Create Courtesy Car Contract",
|
||||
"submit-for-testing": "Submit for Testing",
|
||||
"void": "Void Job"
|
||||
},
|
||||
"jobsdetail": {
|
||||
@@ -2420,6 +2428,60 @@
|
||||
"updated": "Note updated successfully."
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"actions": {
|
||||
"remove": "Remove"
|
||||
},
|
||||
"aria": {
|
||||
"toggle": "Toggle Watching Job"
|
||||
},
|
||||
"channels": {
|
||||
"app": "App",
|
||||
"email": "Email",
|
||||
"fcm": "Push"
|
||||
},
|
||||
"labels": {
|
||||
"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"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"labels": {
|
||||
"noownerinfo": "No owner information."
|
||||
@@ -3416,6 +3478,7 @@
|
||||
"dashboard": "Dashboard",
|
||||
"dms": "DMS Export",
|
||||
"export-logs": "Export Logs",
|
||||
"feature-request": "Feature Requet",
|
||||
"inventory": "Inventory",
|
||||
"jobs": "Jobs",
|
||||
"jobs-active": "Active Jobs",
|
||||
@@ -3460,6 +3523,7 @@
|
||||
"dashboard": "Dashboard | {{app}}",
|
||||
"dms": "DMS Export | {{app}}",
|
||||
"export-logs": "Export Logs | {{app}}",
|
||||
"feature-request": "Feature Request | {{app}}",
|
||||
"imexonline": "ImEX Online",
|
||||
"inventory": "Inventory | {{app}}",
|
||||
"jobs": "Active Jobs | {{app}}",
|
||||
@@ -3677,6 +3741,7 @@
|
||||
"users": {
|
||||
"errors": {
|
||||
"signinerror": {
|
||||
"auth/invalid-email": "A user with this email does not exist.",
|
||||
"auth/user-disabled": "User account disabled. ",
|
||||
"auth/user-not-found": "A user with this email does not exist.",
|
||||
"auth/wrong-password": "The email and password combination you provided is incorrect."
|
||||
@@ -3779,60 +3844,6 @@
|
||||
"validation": {
|
||||
"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": {
|
||||
"cash_discount_percentage": "",
|
||||
"enable_cash_discount": "",
|
||||
"payment_type": "",
|
||||
"payment_map": {
|
||||
"amex": "American Express",
|
||||
"disc": "Discover",
|
||||
@@ -344,7 +343,8 @@
|
||||
"jcb": "JCB",
|
||||
"mast": "MasterCard",
|
||||
"visa": "Visa"
|
||||
}
|
||||
},
|
||||
"payment_type": ""
|
||||
},
|
||||
"invoice_federal_tax_rate": "",
|
||||
"invoice_local_tax_rate": "",
|
||||
@@ -601,7 +601,8 @@
|
||||
"templates": ""
|
||||
},
|
||||
"ss_configuration": {
|
||||
"dailyhrslimit": ""
|
||||
"dailyhrslimit": "",
|
||||
"nobusinessdays": ""
|
||||
},
|
||||
"ssbuckets": {
|
||||
"color": "",
|
||||
@@ -1220,7 +1221,8 @@
|
||||
"errors": {
|
||||
"fcm": "",
|
||||
"notfound": "",
|
||||
"sizelimit": ""
|
||||
"sizelimit": "",
|
||||
"submit-for-testing": ""
|
||||
},
|
||||
"itemtypes": {
|
||||
"contract": "",
|
||||
@@ -1234,6 +1236,7 @@
|
||||
"areyousure": "",
|
||||
"barcode": "código de barras",
|
||||
"cancel": "",
|
||||
"changelog": "",
|
||||
"clear": "",
|
||||
"confirmpassword": "",
|
||||
"created_at": "",
|
||||
@@ -1243,6 +1246,7 @@
|
||||
"errors": "",
|
||||
"excel": "",
|
||||
"exceptiontitle": "",
|
||||
"feature-request": "",
|
||||
"friday": "",
|
||||
"globalsearch": "",
|
||||
"help": "",
|
||||
@@ -1321,6 +1325,7 @@
|
||||
"notfoundtitle": "",
|
||||
"partnernotrunning": "",
|
||||
"rbacunauth": "",
|
||||
"submit-for-testing": "",
|
||||
"unsavedchanges": "Usted tiene cambios no guardados.",
|
||||
"unsavedchangespopup": ""
|
||||
},
|
||||
@@ -1633,6 +1638,7 @@
|
||||
"actual_completion": "Realización real",
|
||||
"actual_delivery": "Entrega real",
|
||||
"actual_in": "Real en",
|
||||
"acv_amount": "",
|
||||
"adjustment_bottom_line": "Ajustes",
|
||||
"adjustmenthours": "",
|
||||
"alt_transport": "",
|
||||
@@ -1758,9 +1764,10 @@
|
||||
"est_ct_ln": "Apellido del tasador",
|
||||
"est_ea": "Correo electrónico del tasador",
|
||||
"est_ph1": "Número de teléfono del tasador",
|
||||
"flat_rate_ats": "",
|
||||
"federal_tax_payable": "Impuesto federal por pagar",
|
||||
"federal_tax_rate": "",
|
||||
"flat_rate_ats": "",
|
||||
"hit_and_run": "",
|
||||
"ins_addr1": "Dirección de Insurance Co.",
|
||||
"ins_city": "Ciudad de seguros",
|
||||
"ins_co_id": "ID de la compañía de seguros",
|
||||
@@ -2314,6 +2321,7 @@
|
||||
"duplicate": "",
|
||||
"duplicatenolines": "",
|
||||
"newcccontract": "",
|
||||
"submit-for-testing": "",
|
||||
"void": ""
|
||||
},
|
||||
"jobsdetail": {
|
||||
@@ -2420,6 +2428,60 @@
|
||||
"updated": "Nota actualizada con éxito."
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"actions": {
|
||||
"remove": ""
|
||||
},
|
||||
"aria": {
|
||||
"toggle": ""
|
||||
},
|
||||
"channels": {
|
||||
"app": "",
|
||||
"email": "",
|
||||
"fcm": ""
|
||||
},
|
||||
"labels": {
|
||||
"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": ""
|
||||
},
|
||||
"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": ""
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"labels": {
|
||||
"noownerinfo": ""
|
||||
@@ -3416,6 +3478,7 @@
|
||||
"dashboard": "",
|
||||
"dms": "",
|
||||
"export-logs": "",
|
||||
"feature-request": "",
|
||||
"inventory": "",
|
||||
"jobs": "",
|
||||
"jobs-active": "",
|
||||
@@ -3460,6 +3523,7 @@
|
||||
"dashboard": "",
|
||||
"dms": "",
|
||||
"export-logs": "",
|
||||
"feature-request": "",
|
||||
"imexonline": "",
|
||||
"inventory": "",
|
||||
"jobs": "Todos los trabajos | {{app}}",
|
||||
@@ -3677,6 +3741,7 @@
|
||||
"users": {
|
||||
"errors": {
|
||||
"signinerror": {
|
||||
"auth/invalid-email": "",
|
||||
"auth/user-disabled": "",
|
||||
"auth/user-not-found": "",
|
||||
"auth/wrong-password": ""
|
||||
@@ -3779,60 +3844,6 @@
|
||||
"validation": {
|
||||
"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": {
|
||||
"cash_discount_percentage": "",
|
||||
"enable_cash_discount": "",
|
||||
"payment_type": "",
|
||||
"payment_map": {
|
||||
"amex": "American Express",
|
||||
"disc": "Discover",
|
||||
@@ -344,7 +343,8 @@
|
||||
"jcb": "JCB",
|
||||
"mast": "MasterCard",
|
||||
"visa": "Visa"
|
||||
}
|
||||
},
|
||||
"payment_type": ""
|
||||
},
|
||||
"invoice_federal_tax_rate": "",
|
||||
"invoice_local_tax_rate": "",
|
||||
@@ -601,7 +601,8 @@
|
||||
"templates": ""
|
||||
},
|
||||
"ss_configuration": {
|
||||
"dailyhrslimit": ""
|
||||
"dailyhrslimit": "",
|
||||
"nobusinessdays": ""
|
||||
},
|
||||
"ssbuckets": {
|
||||
"color": "",
|
||||
@@ -1220,7 +1221,8 @@
|
||||
"errors": {
|
||||
"fcm": "",
|
||||
"notfound": "",
|
||||
"sizelimit": ""
|
||||
"sizelimit": "",
|
||||
"submit-for-testing": ""
|
||||
},
|
||||
"itemtypes": {
|
||||
"contract": "",
|
||||
@@ -1234,6 +1236,7 @@
|
||||
"areyousure": "",
|
||||
"barcode": "code à barre",
|
||||
"cancel": "",
|
||||
"changelog": "",
|
||||
"clear": "",
|
||||
"confirmpassword": "",
|
||||
"created_at": "",
|
||||
@@ -1243,6 +1246,7 @@
|
||||
"errors": "",
|
||||
"excel": "",
|
||||
"exceptiontitle": "",
|
||||
"feature-request": "",
|
||||
"friday": "",
|
||||
"globalsearch": "",
|
||||
"help": "",
|
||||
@@ -1321,6 +1325,7 @@
|
||||
"notfoundtitle": "",
|
||||
"partnernotrunning": "",
|
||||
"rbacunauth": "",
|
||||
"submit-for-testing": "",
|
||||
"unsavedchanges": "Vous avez des changements non enregistrés.",
|
||||
"unsavedchangespopup": ""
|
||||
},
|
||||
@@ -1633,6 +1638,7 @@
|
||||
"actual_completion": "Achèvement réel",
|
||||
"actual_delivery": "Livraison réelle",
|
||||
"actual_in": "En réel",
|
||||
"acv_amount": "",
|
||||
"adjustment_bottom_line": "Ajustements",
|
||||
"adjustmenthours": "",
|
||||
"alt_transport": "",
|
||||
@@ -1758,9 +1764,10 @@
|
||||
"est_ct_ln": "Nom de l'évaluateur",
|
||||
"est_ea": "Courriel 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_rate": "",
|
||||
"flat_rate_ats": "",
|
||||
"hit_and_run": "",
|
||||
"ins_addr1": "Adresse Insurance Co.",
|
||||
"ins_city": "Insurance City",
|
||||
"ins_co_id": "ID de la compagnie d'assurance",
|
||||
@@ -2314,6 +2321,7 @@
|
||||
"duplicate": "",
|
||||
"duplicatenolines": "",
|
||||
"newcccontract": "",
|
||||
"submit-for-testing": "",
|
||||
"void": ""
|
||||
},
|
||||
"jobsdetail": {
|
||||
@@ -2420,6 +2428,60 @@
|
||||
"updated": "Remarque mise à jour avec succès."
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"actions": {
|
||||
"remove": ""
|
||||
},
|
||||
"aria": {
|
||||
"toggle": ""
|
||||
},
|
||||
"channels": {
|
||||
"app": "",
|
||||
"email": "",
|
||||
"fcm": ""
|
||||
},
|
||||
"labels": {
|
||||
"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": ""
|
||||
},
|
||||
"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": ""
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
"labels": {
|
||||
"noownerinfo": ""
|
||||
@@ -3416,6 +3478,7 @@
|
||||
"dashboard": "",
|
||||
"dms": "",
|
||||
"export-logs": "",
|
||||
"feature-request": "",
|
||||
"inventory": "",
|
||||
"jobs": "",
|
||||
"jobs-active": "",
|
||||
@@ -3460,6 +3523,7 @@
|
||||
"dashboard": "",
|
||||
"dms": "",
|
||||
"export-logs": "",
|
||||
"feature-request": "",
|
||||
"imexonline": "",
|
||||
"inventory": "",
|
||||
"jobs": "Tous les emplois | {{app}}",
|
||||
@@ -3677,6 +3741,7 @@
|
||||
"users": {
|
||||
"errors": {
|
||||
"signinerror": {
|
||||
"auth/invalid-email": "",
|
||||
"auth/user-disabled": "",
|
||||
"auth/user-not-found": "",
|
||||
"auth/wrong-password": ""
|
||||
@@ -3779,60 +3844,6 @@
|
||||
"validation": {
|
||||
"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) =>
|
||||
i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }),
|
||||
jobconverted: (ro_number) => i18n.t("audit_trail.messages.jobconverted", { ro_number }),
|
||||
jobintake: (status, email, scheduled_completion) =>
|
||||
i18n.t("audit_trail.messages.jobintake", { status, email, scheduled_completion }),
|
||||
jobintake: (status, scheduled_completion) =>
|
||||
i18n.t("audit_trail.messages.jobintake", { status, scheduled_completion }),
|
||||
jobdelivery: (status, email, actual_completion) =>
|
||||
i18n.t("audit_trail.messages.jobdelivery", { status, email, actual_completion }),
|
||||
jobexported: () => i18n.t("audit_trail.messages.jobexported"),
|
||||
|
||||
@@ -14,10 +14,7 @@ const onServiceWorkerUpdate = (registration) => {
|
||||
<Button
|
||||
onClick={async () => {
|
||||
window.open(
|
||||
InstanceRenderManager({
|
||||
imex: "https://imex-online.noticeable.news/",
|
||||
rome: "https://rome-online.noticeable.news/"
|
||||
}),
|
||||
`https://shopmanagement.canny.io/changelog`,
|
||||
"_blank"
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
export const MockBodyshop = {
|
||||
address1: "123 Fake St",
|
||||
address2: "Unit #100",
|
||||
city: "Vancouver",
|
||||
country: "Canada",
|
||||
created_at: "2019-12-10T20:03:06.420853+00:00",
|
||||
email: "snaptsoft@gmail.com",
|
||||
federal_tax_id: "GST10150492",
|
||||
id: "52b7357c-0edd-4c95-85c3-dfdbcdfad9ac",
|
||||
insurance_vendor_id: "F123456",
|
||||
logo_img_path: "https://www.snapt.ca/assets/logo-placeholder.png",
|
||||
md_ro_statuses: {
|
||||
statuses: [
|
||||
"Open",
|
||||
"Scheduled",
|
||||
"Arrived",
|
||||
"Repair Plan",
|
||||
"Parts",
|
||||
"Body",
|
||||
"Prep",
|
||||
"Paint",
|
||||
"Reassembly",
|
||||
"Sublet",
|
||||
"Detail",
|
||||
"Completed",
|
||||
"Delivered",
|
||||
"Invoiced",
|
||||
"Exported"
|
||||
],
|
||||
open_statuses: ["Open", "Scheduled", "Arrived", "Repair Plan", "Parts", "Body", "Prep", "Paint"],
|
||||
default_arrived: "Arrived",
|
||||
default_exported: "Exported",
|
||||
default_imported: "Open",
|
||||
default_invoiced: "Invoiced",
|
||||
default_completed: "Completed",
|
||||
default_delivered: "Delivered",
|
||||
default_scheduled: "Scheduled"
|
||||
},
|
||||
md_order_statuses: {
|
||||
statuses: ["Ordered", "Received", "Canceled", "Backordered"],
|
||||
default_bo: "Backordered",
|
||||
default_ordered: "Ordered",
|
||||
default_canceled: "Canceled",
|
||||
default_received: "Received"
|
||||
},
|
||||
shopname: "Testing Collision",
|
||||
state: "BC",
|
||||
state_tax_id: "PST1000-2991",
|
||||
updated_at: "2020-03-23T22:06:03.509544+00:00",
|
||||
zip_post: "V6B 1M9",
|
||||
region_config: "CA_BC",
|
||||
md_responsibility_centers: {
|
||||
costs: [
|
||||
"Aftermarket",
|
||||
"ATS",
|
||||
"Body",
|
||||
"Detail",
|
||||
"Daignostic",
|
||||
"Electrical",
|
||||
"Chrome",
|
||||
"Frame",
|
||||
"Mechanical",
|
||||
"Refinish",
|
||||
"Structural",
|
||||
"Existing",
|
||||
"Glass",
|
||||
"LKQ",
|
||||
"OEM",
|
||||
"OEM Partial",
|
||||
"Re-cored",
|
||||
"Remanufactured",
|
||||
"Other",
|
||||
"Sublet",
|
||||
"Towing"
|
||||
],
|
||||
profits: [
|
||||
"Aftermarket",
|
||||
"ATS",
|
||||
"Body",
|
||||
"Detail",
|
||||
"Daignostic",
|
||||
"Electrical",
|
||||
"Chrome",
|
||||
"Frame",
|
||||
"Mechanical",
|
||||
"Refinish",
|
||||
"Structural",
|
||||
"Existing",
|
||||
"Glass",
|
||||
"LKQ",
|
||||
"OEM",
|
||||
"OEM Partial",
|
||||
"Re-cored",
|
||||
"Remanufactured",
|
||||
"Other",
|
||||
"Sublet",
|
||||
"Towing"
|
||||
],
|
||||
defaults: {
|
||||
ATS: "ATS",
|
||||
LAB: "Body",
|
||||
LAD: "Diagnostic",
|
||||
LAE: "Electrical",
|
||||
LAF: "Frame",
|
||||
LAG: "Glass",
|
||||
LAM: "Mechanical",
|
||||
LAR: "Refinish",
|
||||
LAS: "Structural",
|
||||
LAU: "Detail",
|
||||
PAA: "Aftermarket",
|
||||
PAC: "Chrome",
|
||||
PAL: "LKQ",
|
||||
PAM: "Remanufactured",
|
||||
PAN: "OEM",
|
||||
PAO: "Other",
|
||||
PAP: "OEM Partial",
|
||||
PAR: "16",
|
||||
TOW: "Towing"
|
||||
}
|
||||
},
|
||||
employees: [
|
||||
{
|
||||
id: "075b744c-8919-49ca-abb2-ccd51040326d",
|
||||
first_name: "Patrick",
|
||||
last_name: "BODY123",
|
||||
employee_number: "101",
|
||||
cost_center: "Body",
|
||||
__typename: "employees"
|
||||
},
|
||||
{
|
||||
id: "8cc787d3-1cfe-49d3-8a15-8469cd5c2e41",
|
||||
first_name: "Patrick",
|
||||
last_name: "Painter",
|
||||
employee_number: "10211",
|
||||
cost_center: "REFINISH",
|
||||
__typename: "employees"
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -15,7 +15,7 @@ const currentDatePST = new Date()
|
||||
.reverse()
|
||||
.join("-");
|
||||
const sentryRelease =
|
||||
`${import.meta.env.VITE_APP_IS_TEST ? "test" : "production"}-${currentDatePST}-${process.env.VITE_GIT_COMMIT_HASH}`.trim();
|
||||
`${import.meta.env.VITE_APP_IS_TEST ? "test" : "production"}-${currentDatePST}`.trim();
|
||||
|
||||
if (!import.meta.env.DEV) {
|
||||
Sentry.init({
|
||||
|
||||
14
client/tests/Button.test.jsx
Normal file
14
client/tests/Button.test.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
// Button.test.jsx
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { Button } from "antd";
|
||||
import "antd/dist/reset.css"; // Optional: include if needed for styling reset
|
||||
|
||||
describe("AntD Button", () => {
|
||||
it("renders with correct text", () => {
|
||||
render(<Button>Click me</Button>);
|
||||
const button = screen.getByRole("button", { name: /click me/i });
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
6
client/tests/e2e/homepage.e2e.js
Normal file
6
client/tests/e2e/homepage.e2e.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("homepage loads correctly", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page.locator("h1")).toContainText("ImEX Online");
|
||||
});
|
||||
28
client/tests/e2e/signin.e2e.js
Normal file
28
client/tests/e2e/signin.e2e.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { login } from "./utils/login";
|
||||
|
||||
test.describe("SignInComponent", () => {
|
||||
test("successfully logs in with valid credentials", async ({ page }) => {
|
||||
const email = process.env.TEST_USERNAME;
|
||||
const password = process.env.TEST_PASSWORD;
|
||||
|
||||
await login(page, { email, password });
|
||||
|
||||
// Additional assertions after login (optional)
|
||||
await expect(page).toHaveURL(/\/manage\//);
|
||||
});
|
||||
|
||||
test("displays error on invalid credentials", async ({ page }) => {
|
||||
await page.goto("/"); // Adjust if login route differs
|
||||
|
||||
// Fill form with invalid credentials
|
||||
await page.fill('input[placeholder="Username"]', "wronguser@example.com");
|
||||
await page.fill('input[placeholder="Password"]', "wrongpassword");
|
||||
await page.click("button.login-btn");
|
||||
|
||||
// Check for error alert
|
||||
const alert = page.locator(".ant-alert-error");
|
||||
await expect(alert).toBeVisible();
|
||||
await expect(alert).toContainText("A user with this email does not exist.");
|
||||
});
|
||||
});
|
||||
21
client/tests/e2e/utils/login.js
Normal file
21
client/tests/e2e/utils/login.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
export async function login(page, { email, password }) {
|
||||
// Navigate to the login page
|
||||
await page.goto("/"); // Adjust if your login route differs (e.g., '/login')
|
||||
|
||||
// Fill email field
|
||||
await page.fill('input[placeholder="Username"]', email); // Matches Ant Design Input placeholder
|
||||
|
||||
// Fill password field
|
||||
await page.fill('input[placeholder="Password"]', password);
|
||||
|
||||
// Click login button
|
||||
await page.click("button.login-btn");
|
||||
|
||||
// Wait for navigation or success indicator (e.g., redirect to /manage/)
|
||||
await page.waitForURL(/\/manage\//, { timeout: 10000 }); // Adjust based on redirect
|
||||
|
||||
// Verify successful login (e.g., check for a dashboard element)
|
||||
await expect(page.locator("text=Manage")).toBeVisible(); // Adjust to your app’s post-login UI
|
||||
}
|
||||
5
client/tests/setup.js
Normal file
5
client/tests/setup.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { afterEach } from "vitest";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom";
|
||||
|
||||
afterEach(() => cleanup());
|
||||
27
client/tests/setupI18n.js
Normal file
27
client/tests/setupI18n.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import en_Translation from "../src/translations/en_us/common.json";
|
||||
import es_Translation from "../src/translations/es/common.json";
|
||||
import fr_Translation from "../src/translations/fr/common.json";
|
||||
|
||||
const resources = {
|
||||
"en-US": en_Translation,
|
||||
"fr-CA": fr_Translation,
|
||||
"es-MX": es_Translation
|
||||
};
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: "en-US", // Default to en-US for tests (no LanguageDetector)
|
||||
fallbackLng: "en-US",
|
||||
debug: false, // Disable debug in tests
|
||||
react: {
|
||||
useSuspense: false // Disable Suspense for Vitest
|
||||
},
|
||||
interpolation: {
|
||||
escapeValue: false, // React handles XSS
|
||||
skipOnVariables: false
|
||||
}
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -191,11 +191,13 @@ export default defineConfig({
|
||||
"@sentry/react": ["@sentry/react"],
|
||||
"@splitsoftware/splitio-react": ["@splitsoftware/splitio-react"],
|
||||
logrocket: ["logrocket"],
|
||||
"@firebase/analytics": ["@firebase/analytics"],
|
||||
"@firebase/app": ["@firebase/app"],
|
||||
"@firebase/firestore": ["@firebase/firestore"],
|
||||
"@firebase/auth": ["@firebase/auth"],
|
||||
"@firebase/messaging": ["@firebase/messaging"],
|
||||
firebase: [
|
||||
"@firebase/analytics",
|
||||
"@firebase/app",
|
||||
"@firebase/firestore",
|
||||
"@firebase/auth",
|
||||
"@firebase/messaging"
|
||||
],
|
||||
markerjs2: ["markerjs2"],
|
||||
"@apollo/client": ["@apollo/client"],
|
||||
"libphonenumber-js": ["libphonenumber-js"]
|
||||
@@ -218,7 +220,13 @@ export default defineConfig({
|
||||
"react-router-dom",
|
||||
"dayjs",
|
||||
"redux",
|
||||
"react-redux"
|
||||
"react-redux",
|
||||
"@firebase/app",
|
||||
"@firebase/analytics",
|
||||
"@firebase/firestore",
|
||||
"@firebase/auth",
|
||||
"@firebase/messaging",
|
||||
"@firebase/util"
|
||||
],
|
||||
esbuildOptions: {
|
||||
// Update for Vite 6: Use proper file extensions
|
||||
|
||||
19
client/vitest.config.js
Normal file
19
client/vitest.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from "vitest/config";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
setupFiles: "./tests/setup.js",
|
||||
testTimeout: 10000 // 10 seconds (in milliseconds)
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
silenceDeprecations: ["import"] // Suppress @import warnings
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -117,6 +117,7 @@ services:
|
||||
aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/io-ftp-test.key
|
||||
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-job-totals --create-bucket-configuration LocationConstraint=ca-central-1
|
||||
"
|
||||
# Node App: The Main IMEX API
|
||||
node-app:
|
||||
|
||||
77
download-job-totals-fixtures.js
Normal file
77
download-job-totals-fixtures.js
Normal file
@@ -0,0 +1,77 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const logger = require("./server/utils/logger"); // Assuming same logger utility
|
||||
const s3Client = require("./server/utils/s3"); // Using the S3 client utilities with LocalStack support
|
||||
|
||||
// Set bucket name for development with LocalStack
|
||||
const S3_BUCKET_NAME = "imex-job-totals";
|
||||
|
||||
// Set fixtures directory path
|
||||
const FIXTURES_DIR = path.join(__dirname, "server", "job", "test", "fixtures", "job-totals");
|
||||
|
||||
const ensureFixturesDirectory = () => {
|
||||
if (!fs.existsSync(FIXTURES_DIR)) {
|
||||
fs.mkdirSync(FIXTURES_DIR, { recursive: true });
|
||||
logger.log(`Created fixtures directory: ${FIXTURES_DIR}`, "info");
|
||||
}
|
||||
};
|
||||
|
||||
const downloadJsonFiles = async (userInfo = { email: "system" }) => {
|
||||
logger.log(`Starting download of JSON files from bucket: ${S3_BUCKET_NAME}`, "debug", userInfo.email);
|
||||
|
||||
try {
|
||||
ensureFixturesDirectory();
|
||||
const contents = await s3Client.listFilesInS3Bucket(S3_BUCKET_NAME);
|
||||
|
||||
if (!contents.length) {
|
||||
logger.log("No files found in bucket", "info", userInfo.email);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log(`Found ${contents.length} files in bucket`, "info", userInfo.email);
|
||||
|
||||
for (const item of contents) {
|
||||
if (!item.Key.endsWith(".json")) {
|
||||
logger.log(`Skipping non-JSON file: ${item.Key}`, "debug", userInfo.email);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.log(`Downloading: ${item.Key}`, "debug", userInfo.email);
|
||||
const fileData = await s3Client.downloadFileFromS3({
|
||||
bucketName: S3_BUCKET_NAME,
|
||||
key: item.Key
|
||||
});
|
||||
|
||||
const fileContent = await fileData.transformToString();
|
||||
const fileName = path.basename(item.Key);
|
||||
const filePath = path.join(FIXTURES_DIR, fileName);
|
||||
|
||||
fs.writeFileSync(filePath, fileContent);
|
||||
logger.log(`Saved: ${filePath}`, "info", userInfo.email);
|
||||
}
|
||||
|
||||
logger.log("Download completed successfully", "info", userInfo.email);
|
||||
} catch (error) {
|
||||
logger.log("Failed to download JSON files", "error", userInfo.email, null, {
|
||||
error: error?.message,
|
||||
stack: error?.stack
|
||||
});
|
||||
throw error; // Re-throw to trigger process exit with error code
|
||||
}
|
||||
};
|
||||
|
||||
// Run the download if script is executed directly
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
try {
|
||||
await downloadJsonFiles();
|
||||
console.log("Script completed successfully");
|
||||
process.exit(0); // Explicitly exit with success code
|
||||
} catch (error) {
|
||||
console.error("Fatal error downloading files:", error);
|
||||
process.exit(1); // Explicitly exit with error code
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
module.exports = downloadJsonFiles;
|
||||
@@ -31,6 +31,15 @@
|
||||
headers:
|
||||
- name: x-imex-auth
|
||||
value_from_env: DATAPUMP_AUTH
|
||||
- name: Podium Data Pump
|
||||
webhook: '{{HASURA_API_URL}}/data/podium'
|
||||
schedule: 15 5 * * *
|
||||
include_in_metadata: true
|
||||
payload: {}
|
||||
headers:
|
||||
- name: x-imex-auth
|
||||
value_from_env: DATAPUMP_AUTH
|
||||
comment: ""
|
||||
- name: Rome Usage Report
|
||||
webhook: '{{HASURA_API_URL}}/data/usagereport'
|
||||
schedule: 0 12 * * 5
|
||||
|
||||
@@ -965,6 +965,7 @@
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- intellipay_config
|
||||
- intellipay_merchant_id
|
||||
- jc_hourly_rates
|
||||
- jobsizelimit
|
||||
- last_name_first
|
||||
@@ -1004,6 +1005,7 @@
|
||||
- pbs_configuration
|
||||
- pbs_serialnumber
|
||||
- phone
|
||||
- podiumid
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- region_config
|
||||
@@ -1023,6 +1025,7 @@
|
||||
- template_header
|
||||
- textid
|
||||
- timezone
|
||||
- tours_enabled
|
||||
- tt_allow_post_to_invoiced
|
||||
- tt_enforce_hours_for_tech_console
|
||||
- updated_at
|
||||
@@ -3592,6 +3595,7 @@
|
||||
- actual_completion
|
||||
- actual_delivery
|
||||
- actual_in
|
||||
- acv_amount
|
||||
- adj_g_disc
|
||||
- adj_strdis
|
||||
- adj_towdis
|
||||
@@ -3697,6 +3701,7 @@
|
||||
- federal_tax_rate
|
||||
- flat_rate_ats
|
||||
- g_bett_amt
|
||||
- hit_and_run
|
||||
- id
|
||||
- inproduction
|
||||
- ins_addr1
|
||||
@@ -3863,6 +3868,7 @@
|
||||
- actual_completion
|
||||
- actual_delivery
|
||||
- actual_in
|
||||
- acv_amount
|
||||
- adj_g_disc
|
||||
- adj_strdis
|
||||
- adj_towdis
|
||||
@@ -3969,6 +3975,7 @@
|
||||
- federal_tax_rate
|
||||
- flat_rate_ats
|
||||
- g_bett_amt
|
||||
- hit_and_run
|
||||
- id
|
||||
- inproduction
|
||||
- ins_addr1
|
||||
@@ -4147,6 +4154,7 @@
|
||||
- actual_completion
|
||||
- actual_delivery
|
||||
- actual_in
|
||||
- acv_amount
|
||||
- adj_g_disc
|
||||
- adj_strdis
|
||||
- adj_towdis
|
||||
@@ -4253,6 +4261,7 @@
|
||||
- federal_tax_rate
|
||||
- flat_rate_ats
|
||||
- g_bett_amt
|
||||
- hit_and_run
|
||||
- id
|
||||
- inproduction
|
||||
- ins_addr1
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "intellipay_merchant_id" text
|
||||
-- null unique;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "intellipay_merchant_id" text
|
||||
null unique;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "tours_enabled" boolean
|
||||
-- not null default 'true';
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "tours_enabled" boolean
|
||||
not null default 'true';
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "podiumid" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "podiumid" text
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."jobs" add column "hit_and_run" boolean
|
||||
-- null default 'false';
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "hit_and_run" boolean
|
||||
null default 'false';
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."jobs" add column "acv_amount" numeric
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "acv_amount" numeric
|
||||
null;
|
||||
4836
package-lock.json
generated
4836
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user