Compare commits
69 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9f562faa4 | ||
|
|
b8b76cb96c | ||
|
|
fa958cbbfe | ||
|
|
2146672916 | ||
|
|
f96460f332 | ||
|
|
04c70876d0 | ||
|
|
bc6a94eede | ||
|
|
f288b0ee22 | ||
|
|
e54692928b | ||
|
|
0fd8bcb1b1 | ||
|
|
07b18836f5 | ||
|
|
ff08d19d79 | ||
|
|
bd6f300c8d | ||
|
|
ac2fbaf6f7 | ||
|
|
f409acc7fd | ||
|
|
06dcb20b2b | ||
|
|
f4fed0db9d | ||
|
|
8430f500ef | ||
|
|
68584243f4 | ||
|
|
c8f5c3ed9e | ||
|
|
994d7e17aa | ||
|
|
fd1dd6dddd | ||
|
|
1e9b82ba1e | ||
|
|
312795618e | ||
|
|
35b5645d6f | ||
|
|
a49d845f50 | ||
|
|
9d44540ca8 | ||
|
|
cc95d3bd44 | ||
|
|
648c47cde2 | ||
|
|
17cf6e7696 | ||
|
|
7326ffbae6 | ||
|
|
b2f73c4fba | ||
|
|
6628d43e12 | ||
|
|
596132b2af | ||
|
|
a064b8e07e | ||
|
|
a55102b0ae | ||
|
|
9b75993ac1 | ||
|
|
d5e750c1f0 | ||
|
|
8801990d4a | ||
|
|
e8cda88a33 | ||
|
|
a240391a28 | ||
|
|
42660a7dd1 | ||
|
|
f186d9f8be | ||
|
|
71a74c5437 | ||
|
|
6fe4d982f5 | ||
|
|
5ec032d8d6 | ||
|
|
3f58f9a5f5 | ||
|
|
2718a66fb0 | ||
|
|
4c737371e3 | ||
|
|
ee7a3d0bdf | ||
|
|
181af581e5 | ||
|
|
85fcd64220 | ||
|
|
11e2f5d83d | ||
|
|
cbc723fa38 | ||
|
|
da41668b3f | ||
|
|
df008abec9 | ||
|
|
8a97c5109f | ||
|
|
2fdb06fabe | ||
|
|
95c310119f | ||
|
|
ebe1facbd1 | ||
|
|
b8c56c5c24 | ||
|
|
276771a8b7 | ||
|
|
b2239351f6 | ||
|
|
b021992552 | ||
|
|
f5be07d028 | ||
|
|
1202b86529 | ||
|
|
29c99f2dd9 | ||
|
|
3033e84f45 | ||
|
|
18966476e4 |
@@ -9,13 +9,13 @@ orbs:
|
|||||||
jobs:
|
jobs:
|
||||||
imex-api-deploy:
|
imex-api-deploy:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- eb/setup
|
- eb/setup
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
eb init imex-online-production-api -r ca-central-1 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
eb init imex-online-production-api -r ca-central-1 -p "Node.js 22 running on 64bit Amazon Linux 2"
|
||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
|
|
||||||
imex-hasura-migrate:
|
imex-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -52,7 +52,7 @@ jobs:
|
|||||||
pipeline_number: << pipeline.number >>
|
pipeline_number: << pipeline.number >>
|
||||||
imex-app-build:
|
imex-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
steps:
|
steps:
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
|
|
||||||
imex-app-beta-build:
|
imex-app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
- eb/setup
|
- eb/setup
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
eb init romeonline-productionapi -r us-east-2 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
eb init romeonline-productionapi -r us-east-2 -p "Node.js 22 on 64bit Amazon Linux 2"
|
||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
pipeline_number: << pipeline.number >>
|
pipeline_number: << pipeline.number >>
|
||||||
rome-hasura-migrate:
|
rome-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -150,7 +150,7 @@ jobs:
|
|||||||
pipeline_number: << pipeline.number >>
|
pipeline_number: << pipeline.number >>
|
||||||
rome-app-build:
|
rome-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
|
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -181,7 +181,7 @@ jobs:
|
|||||||
|
|
||||||
test-rome-hasura-migrate:
|
test-rome-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -208,7 +208,7 @@ jobs:
|
|||||||
|
|
||||||
test-rome-app-build:
|
test-rome-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
|
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ jobs:
|
|||||||
|
|
||||||
test-hasura-migrate:
|
test-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
parameters:
|
parameters:
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
@@ -266,7 +266,7 @@ jobs:
|
|||||||
|
|
||||||
imex-test-app-build:
|
imex-test-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ jobs:
|
|||||||
|
|
||||||
imex-test-app-beta-build:
|
imex-test-app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:22.13.1
|
||||||
resource_class: large
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ FROM amazonlinux:2023
|
|||||||
|
|
||||||
# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager)
|
# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager)
|
||||||
RUN dnf install -y git \
|
RUN dnf install -y git \
|
||||||
&& curl -sL https://rpm.nodesource.com/setup_20.x | bash - \
|
&& curl -sL https://rpm.nodesource.com/setup_22.x | bash - \
|
||||||
&& dnf install -y nodejs \
|
&& dnf install -y nodejs \
|
||||||
&& dnf clean all
|
&& dnf clean all
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,17 @@
|
|||||||
t = d.getElementsByTagName("script")[0];
|
t = d.getElementsByTagName("script")[0];
|
||||||
t.parentNode.insertBefore(s, t);
|
t.parentNode.insertBefore(s, t);
|
||||||
</script>
|
</script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.$crisp = [];
|
||||||
|
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
|
||||||
|
(function () {
|
||||||
|
d = document;
|
||||||
|
s = d.createElement("script");
|
||||||
|
s.src = "https://client.crisp.chat/l.js";
|
||||||
|
s.async = 1;
|
||||||
|
d.getElementsByTagName("head")[0].appendChild(s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<% } %>
|
<% } %>
|
||||||
<script>
|
<script>
|
||||||
!(function () {
|
!(function () {
|
||||||
|
|||||||
9684
client/package-lock.json
generated
9684
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,74 +8,74 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/pro-layout": "^7.19.12",
|
"@ant-design/pro-layout": "^7.22.0",
|
||||||
"@apollo/client": "^3.11.8",
|
"@apollo/client": "^3.12.6",
|
||||||
"@emotion/is-prop-valid": "^1.3.1",
|
"@emotion/is-prop-valid": "^1.3.1",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.5.0",
|
"@fingerprintjs/fingerprintjs": "^4.5.1",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.2.7",
|
"@reduxjs/toolkit": "^2.5.0",
|
||||||
"@sentry/cli": "^2.36.2",
|
"@sentry/cli": "^2.40.0",
|
||||||
"@sentry/react": "^7.114.0",
|
"@sentry/react": "^7.114.0",
|
||||||
"@splitsoftware/splitio-react": "^1.13.0",
|
"@splitsoftware/splitio-react": "^1.13.0",
|
||||||
"@tanem/react-nprogress": "^5.0.51",
|
"@tanem/react-nprogress": "^5.0.53",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"antd": "^5.20.1",
|
"antd": "^5.23.1",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^3.3.0",
|
"apollo-link-sentry": "^3.3.0",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.9",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"css-box-model": "^1.2.1",
|
"css-box-model": "^1.2.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dayjs-business-days2": "^1.2.2",
|
"dayjs-business-days2": "^1.2.3",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.7",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"firebase": "^10.13.2",
|
"firebase": "^10.13.2",
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.10.0",
|
||||||
"i18next": "^23.15.1",
|
"i18next": "^23.15.1",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.2",
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"libphonenumber-js": "^1.11.9",
|
"libphonenumber-js": "^1.11.18",
|
||||||
"logrocket": "^8.1.2",
|
"logrocket": "^8.1.2",
|
||||||
"markerjs2": "^2.32.2",
|
"markerjs2": "^2.32.3",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"normalize-url": "^8.0.1",
|
"normalize-url": "^8.0.1",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^9.1.0",
|
"query-string": "^9.1.1",
|
||||||
"raf-schd": "^4.0.3",
|
"raf-schd": "^4.0.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-big-calendar": "^1.14.1",
|
"react-big-calendar": "^1.17.1",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^7.2.0",
|
"react-cookie": "^7.2.2",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-drag-listview": "^2.0.0",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.1",
|
"react-grid-gallery": "^1.0.1",
|
||||||
"react-grid-layout": "1.3.4",
|
"react-grid-layout": "1.3.4",
|
||||||
"react-i18next": "^14.1.3",
|
"react-i18next": "^14.1.3",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.4.0",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-image-lightbox": "^5.1.4",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.3",
|
||||||
"react-number-format": "^5.4.2",
|
"react-number-format": "^5.4.3",
|
||||||
"react-popopo": "^2.1.9",
|
"react-popopo": "^2.1.9",
|
||||||
"react-product-fruits": "^2.2.61",
|
"react-product-fruits": "^2.2.61",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.2.0",
|
||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.26.2",
|
||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-virtuoso": "^4.10.4",
|
"react-virtuoso": "^4.10.4",
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.15.0",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"redux-actions": "^3.0.3",
|
"redux-actions": "^3.0.3",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^5.1.1",
|
"reselect": "^5.1.1",
|
||||||
"sass": "^1.79.3",
|
"sass": "^1.83.4",
|
||||||
"socket.io-client": "^4.8.0",
|
"socket.io-client": "^4.8.1",
|
||||||
"styled-components": "^6.1.13",
|
"styled-components": "^6.1.14",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"use-memo-one": "^1.1.3",
|
"use-memo-one": "^1.1.3",
|
||||||
"userpilot": "^1.3.6",
|
"userpilot": "^1.3.6",
|
||||||
@@ -120,36 +120,36 @@
|
|||||||
"@rollup/rollup-linux-x64-gnu": "4.6.1"
|
"@rollup/rollup-linux-x64-gnu": "4.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^5.5.1",
|
"@ant-design/icons": "^5.5.2",
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@babel/preset-react": "^7.24.7",
|
"@babel/preset-react": "^7.26.3",
|
||||||
"@dotenvx/dotenvx": "^1.14.1",
|
"@dotenvx/dotenvx": "^1.33.0",
|
||||||
"@emotion/babel-plugin": "^11.12.0",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.14.0",
|
||||||
"@eslint/js": "^9.15.0",
|
"@eslint/js": "^9.18.0",
|
||||||
"@sentry/webpack-plugin": "^2.22.4",
|
"@sentry/webpack-plugin": "^2.22.4",
|
||||||
"@testing-library/cypress": "^10.0.2",
|
"@testing-library/cypress": "^10.0.2",
|
||||||
"browserslist": "^4.23.3",
|
"browserslist": "^4.24.4",
|
||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.4.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "^13.14.2",
|
"cypress": "^13.17.0",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
"eslint-plugin-react": "^7.37.2",
|
"eslint-plugin-react": "^7.37.4",
|
||||||
"globals": "^15.12.0",
|
"globals": "^15.14.0",
|
||||||
"memfs": "^4.12.0",
|
"memfs": "^4.17.0",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"react-error-overlay": "6.0.11",
|
"react-error-overlay": "6.0.11",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"vite": "^5.4.7",
|
"vite": "^6.0.7",
|
||||||
"vite-plugin-babel": "^1.2.0",
|
"vite-plugin-babel": "^1.3.0",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-node-polyfills": "^0.22.0",
|
"vite-plugin-node-polyfills": "^0.23.0",
|
||||||
"vite-plugin-pwa": "^0.20.5",
|
"vite-plugin-pwa": "^0.21.1",
|
||||||
"vite-plugin-style-import": "^2.0.0",
|
"vite-plugin-style-import": "^2.0.0",
|
||||||
"workbox-window": "^7.1.0"
|
"workbox-window": "^7.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,13 @@
|
|||||||
border-bottom: 1px solid #74695c !important;
|
border-bottom: 1px solid #74695c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This was added because the newest release of ant was making the text color and the background color the same on a selected header
|
||||||
|
// Tried all available tokens (https://ant.design/components/menu?locale=en-US) and even reverted all our custom styles, to no avail
|
||||||
|
// This should be kept an eye on, especially if implementing DARK MODE
|
||||||
|
.ant-menu-submenu-title {
|
||||||
|
color: rgba(255, 255, 255, 0.65) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.imex-table-header {
|
.imex-table-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -135,15 +135,19 @@ const CardPaymentModalComponent = ({
|
|||||||
if (window.intellipay) {
|
if (window.intellipay) {
|
||||||
// eslint-disable-next-line no-eval
|
// eslint-disable-next-line no-eval
|
||||||
eval(response.data);
|
eval(response.data);
|
||||||
SetIntellipayCallbackFunctions();
|
pollForIntelliPay(() => {
|
||||||
window.intellipay.autoOpen();
|
SetIntellipayCallbackFunctions();
|
||||||
|
window.intellipay.autoOpen();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const rg = document.createRange();
|
const rg = document.createRange();
|
||||||
const node = rg.createContextualFragment(response.data);
|
const node = rg.createContextualFragment(response.data);
|
||||||
document.documentElement.appendChild(node);
|
document.documentElement.appendChild(node);
|
||||||
SetIntellipayCallbackFunctions();
|
pollForIntelliPay(() => {
|
||||||
window.intellipay.isAutoOpen = true;
|
SetIntellipayCallbackFunctions();
|
||||||
window.intellipay.initialize();
|
window.intellipay.isAutoOpen = true;
|
||||||
|
window.intellipay.initialize();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.open({
|
notification.open({
|
||||||
@@ -347,3 +351,27 @@ const CardPaymentModalComponent = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(CardPaymentModalComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(CardPaymentModalComponent);
|
||||||
|
|
||||||
|
//Poll for window.IntelliPay.fixAmount for 5 seconds. If it doesn't come up, just try anyways to force the possible error.
|
||||||
|
function pollForIntelliPay(callbackFunction) {
|
||||||
|
const timeout = 5000;
|
||||||
|
const interval = 150; // Poll every 100 milliseconds
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
function checkFixAmount() {
|
||||||
|
if (window.intellipay && window.intellipay.fixAmount !== undefined) {
|
||||||
|
callbackFunction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Date.now() - startTime >= timeout) {
|
||||||
|
console.log("Stopped polling IntelliPay after 10 seconds. Attemping to set functions anyways.");
|
||||||
|
callbackFunction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(checkFixAmount, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFixAmount();
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CardPaymentModalComponent from "./card-payment-modal.component.";
|
import CardPaymentModalComponent from "./card-payment-modal.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
cardPaymentModal: selectCardPayment,
|
cardPaymentModal: selectCardPayment,
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import * as Sentry from "@sentry/react";
|
||||||
import { Button, Col, Collapse, Result, Row, Space } from "antd";
|
import { Button, Col, Collapse, Result, Row, Space } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { withTranslation } from "react-i18next";
|
import { withTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
|
||||||
import * as Sentry from "@sentry/react";
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
|
||||||
@@ -38,28 +38,23 @@ class ErrorBoundary extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleErrorSubmit = () => {
|
handleErrorSubmit = () => {
|
||||||
InstanceRenderManager({
|
window.$crisp.push([
|
||||||
executeFunction: true,
|
"do",
|
||||||
args: [],
|
"message:send",
|
||||||
imex: () => {
|
[
|
||||||
window.$crisp.push([
|
"text",
|
||||||
"do",
|
`I hit the following error: \n\n
|
||||||
"message:send",
|
|
||||||
[
|
|
||||||
"text",
|
|
||||||
`I hit the following error: \n\n
|
|
||||||
${this.state.error.message}\n\n
|
${this.state.error.message}\n\n
|
||||||
${this.state.error.stack}\n\n
|
${this.state.error.stack}\n\n
|
||||||
URL:${window.location} as ${this.props.currentUser.email} for ${
|
URL:${window.location} as ${this.props.currentUser.email} for ${
|
||||||
this.props.bodyshop && this.props.bodyshop.name
|
this.props.bodyshop && this.props.bodyshop.name
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
window.$crisp.push(["do", "chat:open"]);
|
||||||
|
|
||||||
window.$crisp.push(["do", "chat:open"]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue**
|
// const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue**
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
|
|||||||
@@ -656,14 +656,7 @@ function Header({
|
|||||||
icon: <Icon component={QuestionCircleFilled} />,
|
icon: <Icon component={QuestionCircleFilled} />,
|
||||||
label: t("menus.header.help"),
|
label: t("menus.header.help"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.open(
|
window.open("https://help.imex.online/", "_blank");
|
||||||
InstanceRenderManager({
|
|
||||||
imex: "https://help.imex.online/",
|
|
||||||
rome: "https://rometech.com//"
|
|
||||||
}),
|
|
||||||
|
|
||||||
"_blank"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...(InstanceRenderManager({
|
...(InstanceRenderManager({
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Form, Input, Popover, Select, Space, Switch } from "antd";
|
import { Button, Form, Input, Popover, Select, Space, Switch } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useState } from "react";
|
import { some } from "lodash";
|
||||||
|
import React, { useCallback, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -19,7 +20,14 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
jobRO: selectJobReadOnly
|
jobRO: selectJobReadOnly
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
|
dispatch(
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid,
|
||||||
|
operation,
|
||||||
|
type
|
||||||
|
})
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTrail, parentFormIsFieldsTouched }) {
|
export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTrail, parentFormIsFieldsTouched }) {
|
||||||
@@ -29,6 +37,7 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
const allFormValues = Form.useWatch([], form);
|
||||||
|
|
||||||
const handleConvert = async ({ employee_csr, category, ...values }) => {
|
const handleConvert = async ({ employee_csr, category, ...values }) => {
|
||||||
if (parentFormIsFieldsTouched()) {
|
if (parentFormIsFieldsTouched()) {
|
||||||
@@ -71,6 +80,8 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const submitDisabled = useCallback(() => some(allFormValues, (v) => v === undefined), [allFormValues]);
|
||||||
|
|
||||||
const popMenu = (
|
const popMenu = (
|
||||||
<div>
|
<div>
|
||||||
<Form
|
<Form
|
||||||
@@ -79,9 +90,12 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
onFinish={handleConvert}
|
onFinish={handleConvert}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
driveable: true,
|
driveable: true,
|
||||||
towin: false,
|
towin: job.towin,
|
||||||
|
ca_gst_registrant: job.ca_gst_registrant,
|
||||||
employee_csr: job.employee_csr,
|
employee_csr: job.employee_csr,
|
||||||
category: job.category
|
category: job.category,
|
||||||
|
referral_source: job.referral_source,
|
||||||
|
referral_source_extra: job.referral_source_extra ?? ""
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -211,7 +225,7 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button type="primary" danger onClick={() => form.submit()} loading={loading}>
|
<Button disabled={submitDisabled()} type="primary" danger onClick={() => form.submit()} loading={loading}>
|
||||||
{t("jobs.actions.convert")}
|
{t("jobs.actions.convert")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
||||||
@@ -233,11 +247,6 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
form.setFieldsValue({
|
|
||||||
driveable: true,
|
|
||||||
towin: false,
|
|
||||||
employee_csr: job.employee_csr
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("jobs.actions.convert")}
|
{t("jobs.actions.convert")}
|
||||||
|
|||||||
@@ -1,26 +1,23 @@
|
|||||||
import { Collapse, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
import { Collapse, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||||
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||||
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
||||||
import JobsDetailChangeFilehandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
|
import JobsDetailChangeFilehandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
|
||||||
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
|
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
|
||||||
import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component";
|
|
||||||
|
|
||||||
import JobsDetailRatesLabor from "../jobs-detail-rates/jobs-detail-rates.labor.component";
|
import JobsDetailRatesLabor from "../jobs-detail-rates/jobs-detail-rates.labor.component";
|
||||||
import JobsDetailRatesMaterials from "../jobs-detail-rates/jobs-detail-rates.materials.component";
|
import JobsDetailRatesMaterials from "../jobs-detail-rates/jobs-detail-rates.materials.component";
|
||||||
import JobsDetailRatesOther from "../jobs-detail-rates/jobs-detail-rates.other.component";
|
import JobsDetailRatesOther from "../jobs-detail-rates/jobs-detail-rates.other.component";
|
||||||
|
import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component";
|
||||||
import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.component";
|
import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.component";
|
||||||
|
|
||||||
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -199,7 +196,9 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
<Collapse.Panel forceRender key="financial" header={t("menus.jobsdetail.financials")}>
|
<Collapse.Panel forceRender key="financial" header={t("menus.jobsdetail.financials")}>
|
||||||
<JobsDetailRatesChangeButton form={form} />
|
<JobsDetailRatesChangeButton form={form} />
|
||||||
<JobsMarkPstExempt form={form} />
|
{InstanceRenderManager({
|
||||||
|
imex: <JobsMarkPstExempt form={form} />
|
||||||
|
})}
|
||||||
<LayoutFormRow>
|
<LayoutFormRow>
|
||||||
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
|
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
|
||||||
<CurrencyInput min={0} />
|
<CurrencyInput min={0} />
|
||||||
@@ -246,7 +245,6 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
<LayoutFormRow>
|
<LayoutFormRow>
|
||||||
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
|
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
|
||||||
<CurrencyInput />
|
<CurrencyInput />
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|||||||
import { Button, Card, Dropdown, Form, Input, Modal, Popconfirm, Popover, Select, Space } from "antd";
|
import { Button, Card, Dropdown, Form, Input, Modal, Popconfirm, Popover, Select, Space } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import parsePhoneNumber from "libphonenumber-js";
|
import parsePhoneNumber from "libphonenumber-js";
|
||||||
import React, { useContext, useMemo, useState } from "react";
|
import { useContext, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
|
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
|
||||||
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
|
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
|
||||||
@@ -30,8 +31,8 @@ import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
|||||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||||
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
||||||
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
|
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
|
||||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -121,6 +122,7 @@ export function JobsDetailHeaderActions({
|
|||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
const [isCancelScheduleModalVisible, setIsCancelScheduleModalVisible] = useState(false);
|
const [isCancelScheduleModalVisible, setIsCancelScheduleModalVisible] = useState(false);
|
||||||
const [insertAppointment] = useMutation(INSERT_MANUAL_APPT);
|
const [insertAppointment] = useMutation(INSERT_MANUAL_APPT);
|
||||||
const [deleteJob] = useMutation(DELETE_JOB);
|
const [deleteJob] = useMutation(DELETE_JOB);
|
||||||
@@ -200,7 +202,10 @@ export function JobsDetailHeaderActions({
|
|||||||
message: t("appointments.successes.created")
|
message: t("appointments.successes.created")
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.open({ type: "error", message: t("appointments.errors.saving", { error: error.message }) });
|
notification.open({
|
||||||
|
type: "error",
|
||||||
|
message: t("appointments.errors.saving", { error: error.message })
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setVisibility(false);
|
setVisibility(false);
|
||||||
@@ -718,7 +723,13 @@ export function JobsDetailHeaderActions({
|
|||||||
key: "toggleproduction",
|
key: "toggleproduction",
|
||||||
id: "job-actions-toggleproduction",
|
id: "job-actions-toggleproduction",
|
||||||
disabled: !job.converted || jobRO,
|
disabled: !job.converted || jobRO,
|
||||||
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} />
|
label: (
|
||||||
|
<JobsDetailHeaderActionsToggleProduction
|
||||||
|
job={job}
|
||||||
|
refetch={refetch}
|
||||||
|
closeParentMenu={() => setDropdownOpen(false)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -831,7 +842,7 @@ export function JobsDetailHeaderActions({
|
|||||||
id: "job-actions-addtoproduction",
|
id: "job-actions-addtoproduction",
|
||||||
disabled: !job.converted,
|
disabled: !job.converted,
|
||||||
label: t("jobs.actions.addtoproduction"),
|
label: t("jobs.actions.addtoproduction"),
|
||||||
onClick: () => AddToProduction(client, job.id, refetch, notification)
|
onClick: () => AddToProduction(client, job.id, refetch, false, notification)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -964,6 +975,14 @@ export function JobsDetailHeaderActions({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (bodyshop?.md_functionality_toggles?.teams) {
|
||||||
|
menuItems.push({
|
||||||
|
key: "sharetoteams",
|
||||||
|
id: "job-actions-sharetoteams",
|
||||||
|
label: <ShareToTeamsButton noIcon={true} urlOverride={`${window.location.origin}${window.location.pathname}`} />
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
key: "exportcustdata",
|
key: "exportcustdata",
|
||||||
id: "job-actions-exportcustdata",
|
id: "job-actions-exportcustdata",
|
||||||
@@ -1148,7 +1167,7 @@ export function JobsDetailHeaderActions({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Dropdown menu={menu} trigger={["click"]} key="changestatus">
|
<Dropdown menu={menu} trigger={["click"]} key="changestatus" open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
||||||
<Button>
|
<Button>
|
||||||
<span>{t("general.labels.actions")}</span>
|
<span>{t("general.labels.actions")}</span>
|
||||||
<DownCircleFilled />
|
<DownCircleFilled />
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||||
import { Button, Form, Popover, Space } from "antd";
|
import { Button, Form, Popover, Space } from "antd";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { JOB_PRODUCTION_TOGGLE } from "../../graphql/jobs.queries";
|
import { GET_JOB_BY_PK_QUICK_INTAKE, JOB_PRODUCTION_TOGGLE } from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
@@ -13,6 +13,7 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|||||||
import { DateTimeFormatterFunction } from "../../utils/DateFormatter";
|
import { DateTimeFormatterFunction } from "../../utils/DateFormatter";
|
||||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser,
|
//currentUser: selectCurrentUser,
|
||||||
@@ -23,16 +24,40 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation }))
|
insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation }))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsDetailHeaderActionsToggleProduction({ bodyshop, job, jobRO, insertAuditTrail }) {
|
export function JobsDetailHeaderActionsToggleProduction({
|
||||||
|
bodyshop,
|
||||||
|
job,
|
||||||
|
jobRO,
|
||||||
|
refetch,
|
||||||
|
closeParentMenu,
|
||||||
|
insertAuditTrail
|
||||||
|
}) {
|
||||||
const [scenario, setScenario] = useState(false);
|
const [scenario, setScenario] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [popOverVisible, setPopOverVisible] = useState(false);
|
||||||
const [mutationUpdateJob] = useMutation(JOB_PRODUCTION_TOGGLE);
|
const [mutationUpdateJob] = useMutation(JOB_PRODUCTION_TOGGLE);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
|
||||||
|
const [getJobDetails, { loading: jobDetailsLoading }] = useLazyQuery(GET_JOB_BY_PK_QUICK_INTAKE, {
|
||||||
|
variables: { id: job.id },
|
||||||
|
onCompleted: (data) => {
|
||||||
|
if (data?.jobs_by_pk) {
|
||||||
|
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,
|
||||||
|
actual_completion: data.jobs_by_pk.actual_completion,
|
||||||
|
scheduled_delivery: data.jobs_by_pk.scheduled_delivery,
|
||||||
|
actual_delivery: data.jobs_by_pk.actual_delivery
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//Figure out what scenario were in, populate accodingly
|
//Figure out what scenario were in, populate accordingly
|
||||||
if (job && bodyshop) {
|
if (job && bodyshop) {
|
||||||
if (bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status)) {
|
if (bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status)) {
|
||||||
setScenario("pre");
|
setScenario("pre");
|
||||||
@@ -76,89 +101,90 @@ export function JobsDetailHeaderActionsToggleProduction({ bodyshop, job, jobRO,
|
|||||||
DateTimeFormatterFunction(values.actual_completion)
|
DateTimeFormatterFunction(values.actual_completion)
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
setPopOverVisible(false);
|
||||||
|
closeParentMenu();
|
||||||
|
refetch();
|
||||||
}
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const popMenu = (
|
const popMenu = (
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
<Form
|
{jobDetailsLoading ? (
|
||||||
layout="vertical"
|
<LoadingSpinner />
|
||||||
form={form}
|
) : (
|
||||||
onFinish={handleConvert}
|
<Form layout="vertical" form={form} onFinish={handleConvert}>
|
||||||
initialValues={{
|
{scenario === "pre" && (
|
||||||
actual_in: dayjs(),
|
<>
|
||||||
scheduled_completion: job.scheduled_completion,
|
<Form.Item
|
||||||
actual_completion: job.actual_completion,
|
name={["actual_in"]}
|
||||||
scheduled_deliver: job.scheduled_deliver,
|
label={t("jobs.fields.actual_in")}
|
||||||
actual_delivery: job.actual_delivery
|
rules={[
|
||||||
}}
|
{
|
||||||
>
|
required: true
|
||||||
{scenario === "pre" && (
|
//message: t("general.validation.required"),
|
||||||
<>
|
}
|
||||||
<Form.Item
|
]}
|
||||||
name={["actual_in"]}
|
>
|
||||||
label={t("jobs.fields.actual_in")}
|
<FormDateTimePickerComponent disabled={jobRO} />
|
||||||
rules={[
|
</Form.Item>
|
||||||
{
|
<Form.Item
|
||||||
required: true
|
name={["scheduled_completion"]}
|
||||||
//message: t("general.validation.required"),
|
label={t("jobs.fields.scheduled_completion")}
|
||||||
}
|
rules={[
|
||||||
]}
|
{
|
||||||
>
|
required: true
|
||||||
<FormDateTimePickerComponent disabled={jobRO} />
|
//message: t("general.validation.required"),
|
||||||
</Form.Item>
|
}
|
||||||
<Form.Item
|
]}
|
||||||
name={["scheduled_completion"]}
|
>
|
||||||
label={t("jobs.fields.scheduled_completion")}
|
<FormDateTimePickerComponent disabled={jobRO} />
|
||||||
rules={[
|
</Form.Item>
|
||||||
{
|
<Form.Item name={["scheduled_delivery"]} label={t("jobs.fields.scheduled_delivery")}>
|
||||||
required: true
|
<FormDateTimePickerComponent disabled={jobRO} />
|
||||||
//message: t("general.validation.required"),
|
</Form.Item>
|
||||||
}
|
</>
|
||||||
]}
|
)}
|
||||||
>
|
{scenario === "prod" && (
|
||||||
<FormDateTimePickerComponent disabled={jobRO} />
|
<>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
<Form.Item name={["scheduled_delivery"]} label={t("jobs.fields.scheduled_delivery")}>
|
name={["actual_completion"]}
|
||||||
<FormDateTimePickerComponent disabled={jobRO} />
|
label={t("jobs.fields.actual_completion")}
|
||||||
</Form.Item>
|
rules={[
|
||||||
</>
|
{
|
||||||
)}
|
required: true
|
||||||
{scenario === "prod" && (
|
//message: t("general.validation.required"),
|
||||||
<>
|
}
|
||||||
<Form.Item
|
]}
|
||||||
name={["actual_completion"]}
|
>
|
||||||
label={t("jobs.fields.actual_completion")}
|
<FormDateTimePickerComponent disabled={jobRO} />
|
||||||
rules={[
|
</Form.Item>
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<FormDateTimePickerComponent disabled={jobRO} />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item name={["actual_delivery"]} label={t("jobs.fields.actual_delivery")}>
|
<Form.Item name={["actual_delivery"]} label={t("jobs.fields.actual_delivery")}>
|
||||||
<FormDateTimePickerComponent disabled={jobRO} />
|
<FormDateTimePickerComponent disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button type="primary" onClick={() => form.submit()} loading={loading}>
|
<Button type="primary" onClick={() => form.submit()} loading={loading}>
|
||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Form>
|
</Form>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover //open={open}
|
<Popover //open={open}
|
||||||
content={popMenu}
|
content={popMenu}
|
||||||
onClick={(e) => e.stopPropagation()}
|
open={popOverVisible}
|
||||||
|
onOpenChange={setPopOverVisible}
|
||||||
|
onClick={(e) => {
|
||||||
|
getJobDetails();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
getPopupContainer={(trigger) => trigger.parentNode}
|
getPopupContainer={(trigger) => trigger.parentNode}
|
||||||
trigger="click"
|
trigger="click"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { useApolloClient } from "@apollo/client";
|
import { useApolloClient } from "@apollo/client";
|
||||||
import { Button, Form, Popover, Space } from "antd";
|
import { Button, Form, Popover, Space } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries";
|
import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -134,7 +134,7 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages, callback
|
|||||||
]}
|
]}
|
||||||
name={"jobid"}
|
name={"jobid"}
|
||||||
>
|
>
|
||||||
<JobSearchSelect />
|
<JobSearchSelect notExported={false} notInvoiced={false} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
<Space>
|
<Space>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Form, Popover, Space } from "antd";
|
import { Button, Form, Popover, Space } from "antd";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -60,7 +60,7 @@ export function JobsDocumentsLocalGalleryReassign({ bodyshop, jobid, allMedia, g
|
|||||||
]}
|
]}
|
||||||
name={"jobid"}
|
name={"jobid"}
|
||||||
>
|
>
|
||||||
<JobSearchSelect />
|
<JobSearchSelect notExported={false} notInvoiced={false}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
<Space>
|
<Space>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
|||||||
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
|
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
|
||||||
import PrintWrapper from "../print-wrapper/print-wrapper.component";
|
import PrintWrapper from "../print-wrapper/print-wrapper.component";
|
||||||
import PartsOrderDrawer from "./parts-order-list-table-drawer.component";
|
import PartsOrderDrawer from "./parts-order-list-table-drawer.component";
|
||||||
|
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobRO: selectJobReadOnly,
|
jobRO: selectJobReadOnly,
|
||||||
@@ -66,19 +67,20 @@ export function PartsOrderListTableComponent({
|
|||||||
|
|
||||||
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
||||||
const { refetch } = billsQuery;
|
const { refetch } = billsQuery;
|
||||||
|
|
||||||
const recordActions = (record, showView = false) => (
|
const recordActions = (record, showView = false) => (
|
||||||
<Space direction="horizontal" wrap>
|
<Space direction="horizontal" wrap>
|
||||||
|
<ShareToTeamsButton
|
||||||
|
linkText={""}
|
||||||
|
urlOverride={`${window.location.origin}/manage/jobs/${job.id}?partsorderid=${record.id}&tab=partssublet `}
|
||||||
|
/>
|
||||||
{showView && (
|
{showView && (
|
||||||
<Button
|
<Button
|
||||||
|
icon={<EyeFilled />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleOnRowClick(record);
|
handleOnRowClick(record);
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<EyeFilled />
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
disabled={jobRO || record.return || record.vendor.id === bodyshop.inhousevendorid}
|
disabled={jobRO || record.return || record.vendor.id === bodyshop.inhousevendorid}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -106,6 +108,7 @@ export function PartsOrderListTableComponent({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
title={t("tasks.buttons.create")}
|
title={t("tasks.buttons.create")}
|
||||||
|
icon={<FaTasks />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTaskUpsertContext({
|
setTaskUpsertContext({
|
||||||
context: {
|
context: {
|
||||||
@@ -114,9 +117,7 @@ export function PartsOrderListTableComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<FaTasks />
|
|
||||||
</Button>
|
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title={t("parts_orders.labels.confirmdelete")}
|
title={t("parts_orders.labels.confirmdelete")}
|
||||||
disabled={jobRO}
|
disabled={jobRO}
|
||||||
@@ -137,9 +138,7 @@ export function PartsOrderListTableComponent({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button disabled={jobRO}>
|
<Button disabled={jobRO} icon={<DeleteFilled />} />
|
||||||
<DeleteFilled />
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import { Button, Input, Space, Spin } from "antd";
|
import { Button, Input, Space, Spin } from "antd";
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { ExclamationCircleFilled, ExclamationCircleOutlined } from "@ant-design/icons";
|
import {
|
||||||
|
ExclamationCircleFilled,
|
||||||
|
ExclamationCircleOutlined,
|
||||||
|
UserDeleteOutlined,
|
||||||
|
UsergroupDeleteOutlined
|
||||||
|
} from "@ant-design/icons";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
|
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
|
||||||
|
|
||||||
@@ -19,12 +23,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardFilte
|
|||||||
|
|
||||||
export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading }) {
|
export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [alertFilter, setAlertFilter] = useState(false);
|
|
||||||
|
|
||||||
const toggleAlertFilter = () => {
|
const toggleAlertFilter = () => {
|
||||||
const newAlertFilter = !alertFilter;
|
setFilter({ ...filter, alert: !filter.alert });
|
||||||
setAlertFilter(newAlertFilter);
|
};
|
||||||
setFilter({ ...filter, alert: newAlertFilter });
|
|
||||||
|
const toggleUnassignedFilter = () => {
|
||||||
|
setFilter({ ...filter, unassigned: !filter.unassigned });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -46,12 +51,19 @@ export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading })
|
|||||||
allowClear
|
allowClear
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type={alertFilter ? "primary" : "default"}
|
type={filter?.alert ? "primary" : "default"}
|
||||||
onClick={toggleAlertFilter}
|
onClick={toggleAlertFilter}
|
||||||
icon={alertFilter ? <ExclamationCircleFilled /> : <ExclamationCircleOutlined />}
|
icon={filter?.alert ? <ExclamationCircleFilled /> : <ExclamationCircleOutlined />}
|
||||||
>
|
>
|
||||||
{t("production.labels.alerts")}
|
{t("production.labels.alerts")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type={filter?.unassigned ? "primary" : "default"}
|
||||||
|
onClick={toggleUnassignedFilter}
|
||||||
|
icon={filter?.unassigned ? <UserDeleteOutlined /> : <UsergroupDeleteOutlined />}
|
||||||
|
>
|
||||||
|
{t("production.labels.unassigned")}
|
||||||
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import dayjs from "../../utils/day";
|
|||||||
|
|
||||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
|
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
|
||||||
|
import { PiMicrosoftTeamsLogo } from "react-icons/pi";
|
||||||
|
|
||||||
const cardColor = (ssbuckets, totalHrs) => {
|
const cardColor = (ssbuckets, totalHrs) => {
|
||||||
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
|
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
|
||||||
@@ -417,9 +419,20 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
|
|||||||
title={!isBodyEmpty ? headerContent : null}
|
title={!isBodyEmpty ? headerContent : null}
|
||||||
extra={
|
extra={
|
||||||
!isBodyEmpty && (
|
!isBodyEmpty && (
|
||||||
<Link to={{ search: `?selected=${card.id}` }}>
|
<Space>
|
||||||
<EyeFilled />
|
<ShareToTeamsButton
|
||||||
</Link>
|
noIcon={true}
|
||||||
|
linkText={
|
||||||
|
<div className="share-to-teams-badge">
|
||||||
|
<PiMicrosoftTeamsLogo />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
urlOverride={`${window.location.origin}/manage/jobs/${card.id}`}
|
||||||
|
/>
|
||||||
|
<Link to={{ search: `?selected=${card.id}` }}>
|
||||||
|
<EyeFilled />
|
||||||
|
</Link>
|
||||||
|
</Space>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -10,6 +10,16 @@
|
|||||||
.height-preserving-container {
|
.height-preserving-container {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.share-to-teams-badge {
|
||||||
|
background-color: #cccccc;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
.react-trello-column-header {
|
.react-trello-column-header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const sortByParentId = (arr) => {
|
|||||||
|
|
||||||
// Function to create board data based on statuses and jobs, with optional filtering
|
// Function to create board data based on statuses and jobs, with optional filtering
|
||||||
export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
|
export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
|
||||||
const { search, employeeId, alert } = filter;
|
const { search, employeeId, alert, unassigned } = filter;
|
||||||
|
|
||||||
const lanes = statuses.map((status) => ({
|
const lanes = statuses.map((status) => ({
|
||||||
id: status,
|
id: status,
|
||||||
@@ -40,6 +40,13 @@ export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
|
|||||||
let filteredJobs =
|
let filteredJobs =
|
||||||
(search === "" || !search) && !employeeId ? data : data.filter((job) => checkFilter(search, employeeId, job));
|
(search === "" || !search) && !employeeId ? data : data.filter((job) => checkFilter(search, employeeId, job));
|
||||||
|
|
||||||
|
// Apply "Unassigned" filter
|
||||||
|
if (unassigned) {
|
||||||
|
filteredJobs = filteredJobs.filter(
|
||||||
|
(job) => !job.employee_body && !job.employee_prep && !job.employee_refinish && !job.employee_csr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Filter jobs by selectedMdInsCos if it has values
|
// Filter jobs by selectedMdInsCos if it has values
|
||||||
if (cardSettings?.selectedMdInsCos?.length > 0) {
|
if (cardSettings?.selectedMdInsCos?.length > 0) {
|
||||||
filteredJobs = filteredJobs.filter((job) => cardSettings.selectedMdInsCos.includes(job.ins_co_nm));
|
filteredJobs = filteredJobs.filter((job) => cardSettings.selectedMdInsCos.includes(job.ins_co_nm));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Card, Col, Form, Popover, Row, Tabs } from "antd";
|
import { Button, Card, Col, Form, Popover, Row, Tabs } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js";
|
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js";
|
||||||
import { defaultKanbanSettings, mergeWithDefaults } from "./defaultKanbanSettings.js";
|
import { defaultKanbanSettings, mergeWithDefaults } from "./defaultKanbanSettings.js";
|
||||||
@@ -11,6 +11,7 @@ import FilterSettings from "./FilterSettings.jsx";
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { isFunction } from "lodash";
|
import { isFunction } from "lodash";
|
||||||
import { useNotification } from "../../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import { SettingOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) {
|
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -153,7 +154,7 @@ function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bod
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover content={overlay} open={open} placement="topRight">
|
<Popover content={overlay} open={open} placement="topRight">
|
||||||
<Button loading={loading} onClick={() => setOpen(!open)}>
|
<Button icon={<SettingOutlined />} loading={loading} onClick={() => setOpen(!open)}>
|
||||||
{t("production.settings.board_settings")}
|
{t("production.settings.board_settings")}
|
||||||
</Button>
|
</Button>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import ProductionListColumnNote from "./production-list-columns.productionnote.c
|
|||||||
import ProductionListColumnCategory from "./production-list-columns.status.category";
|
import ProductionListColumnCategory from "./production-list-columns.status.category";
|
||||||
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
||||||
import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||||
|
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
|
||||||
|
|
||||||
const getEmployeeName = (employeeId, employees) => {
|
const getEmployeeName = (employeeId, employees) => {
|
||||||
const employee = employees.find((e) => e.id === employeeId);
|
const employee = employees.find((e) => e.id === employeeId);
|
||||||
@@ -41,7 +42,17 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
dataIndex: "viewdetail",
|
dataIndex: "viewdetail",
|
||||||
key: "viewdetail",
|
key: "viewdetail",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (text, record) => <Link to={{ search: `?selected=${record.id}` }}>{i18n.t("general.labels.view")}</Link>
|
render: (text, record) => (
|
||||||
|
<Space>
|
||||||
|
<Link to={{ search: `?selected=${record.id}` }}>{i18n.t("general.labels.view")}</Link>
|
||||||
|
<ShareToTeamsButton
|
||||||
|
noIcon={true}
|
||||||
|
linkText={"Share"}
|
||||||
|
noIconStyle={{ color: "#1890ff" }}
|
||||||
|
urlOverride={`${window.location.origin}/manage/jobs/${record.id}`}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
},
|
},
|
||||||
...(Enhanced_Payroll.treatment === "on"
|
...(Enhanced_Payroll.treatment === "on"
|
||||||
? [
|
? [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Dropdown } from "antd";
|
import { Button, Dropdown } from "antd";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
@@ -7,6 +7,7 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import { PrinterFilled } from "@ant-design/icons";
|
||||||
|
|
||||||
const ProdTemplates = TemplateList("production");
|
const ProdTemplates = TemplateList("production");
|
||||||
const { production_by_technician_one, production_by_category_one, production_by_repair_status_one } =
|
const { production_by_technician_one, production_by_category_one, production_by_repair_status_one } =
|
||||||
@@ -123,7 +124,9 @@ export function ProductionListPrint({ bodyshop }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown trigger="click" menu={menu}>
|
<Dropdown trigger="click" menu={menu}>
|
||||||
<Button loading={loading}>{t("general.labels.print")}</Button>
|
<Button icon={<PrinterFilled />} loading={loading}>
|
||||||
|
{t("general.labels.print")}
|
||||||
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const ret = {
|
|||||||
"bills:reexport": 3,
|
"bills:reexport": 3,
|
||||||
|
|
||||||
"employees:page": 5,
|
"employees:page": 5,
|
||||||
|
"employee_teams:page": 5,
|
||||||
|
|
||||||
"owners:list": 2,
|
"owners:list": 2,
|
||||||
"owners:detail": 3,
|
"owners:detail": 3,
|
||||||
@@ -67,6 +68,9 @@ const ret = {
|
|||||||
"timetickets:list": 3,
|
"timetickets:list": 3,
|
||||||
"timetickets:edit": 4,
|
"timetickets:edit": 4,
|
||||||
"timetickets:shiftedit": 5,
|
"timetickets:shiftedit": 5,
|
||||||
|
"timetickets:editcommitted": 5,
|
||||||
|
"ttapprovals:view": 5,
|
||||||
|
"ttapprovals:approve": 5,
|
||||||
|
|
||||||
"users:editaccess": 4,
|
"users:editaccess": 4,
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { Button } from "antd";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { PiMicrosoftTeamsLogo } from "react-icons/pi";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShareToTeamsButton component for sharing content to Microsoft Teams via an HTTP link.
|
||||||
|
*
|
||||||
|
* This component creates a button or link that opens the Microsoft Teams share dialog with
|
||||||
|
* the provided URL, title, and message text through query parameters. The popup window is centered on the screen.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The component's props.
|
||||||
|
* @param {string} [props.messageTextOverride] - Custom message text for sharing.
|
||||||
|
* @param {string} [props.urlOverride] - Custom URL to share instead of the current page's URL.
|
||||||
|
* @param {string} [props.pageTitleOverride] - Custom title for the shared page.
|
||||||
|
* @param {boolean} [props.noIcon=false] - If true, renders as a simple text link instead of a button with an icon.
|
||||||
|
* @param {Object} [props.noIconStyle={}] - Style object for the text link when noIcon is true.
|
||||||
|
* @param {Object} [props.buttonStyle={}] - Style object for the Ant Design button.
|
||||||
|
* @param {Object} [props.buttonIconStyle={}] - Style object for the icon within the button.
|
||||||
|
* @param {string} [props.linkText] - Text to display on the button or link.
|
||||||
|
* @returns {React.ReactElement} A button or text link for sharing to Microsoft Teams.
|
||||||
|
*/
|
||||||
|
const ShareToTeamsComponent = ({
|
||||||
|
bodyshop,
|
||||||
|
messageTextOverride,
|
||||||
|
urlOverride,
|
||||||
|
pageTitleOverride,
|
||||||
|
noIcon = false,
|
||||||
|
noIconStyle = {},
|
||||||
|
buttonStyle = {},
|
||||||
|
buttonIconStyle = {},
|
||||||
|
linkText
|
||||||
|
}) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const currentUrl =
|
||||||
|
urlOverride ||
|
||||||
|
encodeURIComponent(`${window.location.origin}${location.pathname}${location.search}${location.hash}`);
|
||||||
|
const pageTitle =
|
||||||
|
pageTitleOverride ||
|
||||||
|
encodeURIComponent(typeof document !== "undefined" ? document.title : t("general.actions.sharetoteams"));
|
||||||
|
const messageText = messageTextOverride || encodeURIComponent(t("general.actions.sharetoteams"));
|
||||||
|
|
||||||
|
// Construct the Teams share URL with parameters
|
||||||
|
const teamsShareUrl = `https://teams.microsoft.com/share?href=${currentUrl}&preText=${messageText}&title=${pageTitle}`;
|
||||||
|
|
||||||
|
// Function to open the centered share link in a new window/tab
|
||||||
|
const handleShare = () => {
|
||||||
|
const screenWidth = window.screen.width;
|
||||||
|
const screenHeight = window.screen.height;
|
||||||
|
const windowWidth = 600;
|
||||||
|
const windowHeight = 400;
|
||||||
|
|
||||||
|
const left = screenWidth / 2 - windowWidth / 2;
|
||||||
|
const top = screenHeight / 2 - windowHeight / 2;
|
||||||
|
|
||||||
|
const windowFeatures = `width=${windowWidth},height=${windowHeight},left=${left},top=${top}`;
|
||||||
|
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
window.open(teamsShareUrl, "_blank", windowFeatures);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Feature is disabled
|
||||||
|
if (!bodyshop?.md_functionality_toggles?.teams) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (noIcon) {
|
||||||
|
return (
|
||||||
|
<div style={{ cursor: "pointer", ...noIconStyle }} onClick={handleShare}>
|
||||||
|
{!linkText ? t("general.actions.sharetoteams") : linkText}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
backgroundColor: "#6264A7",
|
||||||
|
borderColor: "#6264A7",
|
||||||
|
color: "#FFFFFF",
|
||||||
|
...buttonStyle
|
||||||
|
}}
|
||||||
|
icon={<PiMicrosoftTeamsLogo style={{ color: "#FFFFFF", ...buttonIconStyle }} />}
|
||||||
|
onClick={handleShare}
|
||||||
|
title={linkText === null ? t("general.actions.sharetoteams") : linkText}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ShareToTeamsComponent.propTypes = {
|
||||||
|
messageTextOverride: PropTypes.string,
|
||||||
|
urlOverride: PropTypes.string,
|
||||||
|
pageTitleOverride: PropTypes.string,
|
||||||
|
noIcon: PropTypes.bool,
|
||||||
|
noIconStyle: PropTypes.object,
|
||||||
|
buttonStyle: PropTypes.object,
|
||||||
|
buttonIconStyle: PropTypes.object,
|
||||||
|
linkText: PropTypes.oneOfType([PropTypes.string, PropTypes.node])
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(ShareToTeamsComponent);
|
||||||
@@ -14,7 +14,7 @@ import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
|||||||
import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
// TODO: Client Update, this might break
|
|
||||||
const timeZonesList = Intl.supportedValuesOf("timeZone");
|
const timeZonesList = Intl.supportedValuesOf("timeZone");
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -642,6 +642,15 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
<LayoutFormRow header={t("bodyshop.labels.shop_enabled_features")} id="sharing">
|
||||||
|
<Form.Item
|
||||||
|
label={t("general.actions.sharetoteams")}
|
||||||
|
valuePropName="checked"
|
||||||
|
name={["md_functionality_toggles", "teams"]}
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")} id="messagingpresets">
|
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")} id="messagingpresets">
|
||||||
<Form.List name={["md_messaging_presets"]}>
|
<Form.List name={["md_messaging_presets"]}>
|
||||||
{(fields, { add, remove, move }) => {
|
{(fields, { add, remove, move }) => {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
|||||||
import { pageLimit } from "../../utils/config";
|
import { pageLimit } from "../../utils/config";
|
||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
|
import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task List Component
|
* Task List Component
|
||||||
@@ -266,8 +267,13 @@ function TaskListComponent({
|
|||||||
width: "8%",
|
width: "8%",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Space direction="horizontal">
|
<Space direction="horizontal">
|
||||||
|
<ShareToTeamsButton
|
||||||
|
linkText=""
|
||||||
|
urlOverride={`${window.location.origin}/manage/tasks/alltasks?taskid=${record.id}`}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
title={t("tasks.buttons.edit")}
|
title={t("tasks.buttons.edit")}
|
||||||
|
icon={<EditFilled />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setTaskUpsertContext({
|
setTaskUpsertContext({
|
||||||
context: {
|
context: {
|
||||||
@@ -276,18 +282,18 @@ function TaskListComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<EditFilled />
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
title={t("tasks.buttons.complete")}
|
title={t("tasks.buttons.complete")}
|
||||||
onClick={() => toggleCompletedStatus(record.id, record.completed)}
|
onClick={() => toggleCompletedStatus(record.id, record.completed)}
|
||||||
>
|
icon={record.completed ? <CheckCircleOutlined /> : <CheckCircleFilled />}
|
||||||
{record.completed ? <CheckCircleOutlined /> : <CheckCircleFilled />}
|
/>
|
||||||
</Button>
|
|
||||||
<Button title={t("tasks.buttons.delete")} onClick={() => toggleDeletedStatus(record.id, record.deleted)}>
|
<Button
|
||||||
{record.deleted ? <DeleteOutlined /> : <DeleteFilled />}
|
title={t("tasks.buttons.delete")}
|
||||||
</Button>
|
onClick={() => toggleDeletedStatus(record.id, record.deleted)}
|
||||||
|
icon={record.deleted ? <DeleteOutlined /> : <DeleteFilled />}
|
||||||
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
|
|||||||
const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK);
|
const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK);
|
||||||
const [updateTask] = useMutation(MUTATION_UPDATE_TASK);
|
const [updateTask] = useMutation(MUTATION_UPDATE_TASK);
|
||||||
const { open, context } = taskUpsert;
|
const { open, context } = taskUpsert;
|
||||||
const { jobid, joblineid, billid, partsorderid, taskId, existingTask, query } = context;
|
const { jobid, joblineid, billid, partsorderid, taskId, existingTask, query, view } = context;
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [selectedJobId, setSelectedJobId] = useState(null);
|
const [selectedJobId, setSelectedJobId] = useState(null);
|
||||||
const [selectedJobDetails, setSelectedJobDetails] = useState(null);
|
const [selectedJobDetails, setSelectedJobDetails] = useState(null);
|
||||||
@@ -257,10 +257,15 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")}
|
title={
|
||||||
|
<span id="task-upsert-modal-title">
|
||||||
|
{view ? t("tasks.actions.view") : existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
open={open}
|
open={open}
|
||||||
okText={t("general.actions.save")}
|
okText={t("general.actions.save")}
|
||||||
width="50%"
|
width="50%"
|
||||||
|
cancelText={!isTouched ? t("general.actions.ok") : t("general.actions.cancel")}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
removeTaskIdFromUrl();
|
removeTaskIdFromUrl();
|
||||||
form.submit();
|
form.submit();
|
||||||
@@ -285,6 +290,7 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
|
|||||||
loading={loading || (taskId && taskLoading)}
|
loading={loading || (taskId && taskLoading)}
|
||||||
error={error}
|
error={error}
|
||||||
data={data}
|
data={data}
|
||||||
|
view={view}
|
||||||
existingTask={existingTask || taskData?.tasks_by_pk}
|
existingTask={existingTask || taskData?.tasks_by_pk}
|
||||||
selectedJobId={selectedJobId}
|
selectedJobId={selectedJobId}
|
||||||
setSelectedJobId={setSelectedJobId}
|
setSelectedJobId={setSelectedJobId}
|
||||||
|
|||||||
@@ -2553,3 +2553,16 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const GET_JOB_BY_PK_QUICK_INTAKE = gql`
|
||||||
|
query GET_JOB_BY_PK_QUICK_INTAKE($id: uuid!) {
|
||||||
|
jobs_by_pk(id: $id) {
|
||||||
|
id
|
||||||
|
actual_in
|
||||||
|
scheduled_completion
|
||||||
|
actual_completion
|
||||||
|
scheduled_delivery
|
||||||
|
actual_delivery
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled, PrinterFilled } from "@ant-design/icons";
|
||||||
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
import { useApolloClient, useMutation } from "@apollo/client";
|
import { useApolloClient, useMutation } from "@apollo/client";
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
@@ -16,32 +17,31 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
Typography
|
Typography
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
import { useState } from "react";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
// import { useNavigate } from 'react-router-dom';
|
// import { useNavigate } from 'react-router-dom';
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component";
|
import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component";
|
||||||
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
import CurrencyInput from "../../components/form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../../components/form-items-formatted/currency-form-item.component";
|
||||||
|
import JobCloseRoGuardContainer from "../../components/job-close-ro-guard/job-close-ro-guard.container";
|
||||||
import JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
|
import JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
|
||||||
import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component";
|
import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component";
|
||||||
import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.component";
|
import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.component";
|
||||||
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
|
||||||
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
|
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions.js";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import JobCloseRoGuardContainer from "../../components/job-close-ro-guard/job-close-ro-guard.container";
|
import dayjs from "../../utils/day";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -49,10 +49,17 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||||
|
setPrintCenterContext: (context) =>
|
||||||
|
dispatch(
|
||||||
|
setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "printCenter"
|
||||||
|
})
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
|
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, setPrintCenterContext }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -171,7 +178,6 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
|
|||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<JobsCloseAutoAllocate joblines={job.joblines} form={form} disabled={!!job.date_exported || jobRO} />
|
<JobsCloseAutoAllocate joblines={job.joblines} form={form} disabled={!!job.date_exported || jobRO} />
|
||||||
|
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
onConfirm={() => form.submit()}
|
onConfirm={() => form.submit()}
|
||||||
disabled={jobRO}
|
disabled={jobRO}
|
||||||
@@ -188,6 +194,21 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
|
|||||||
<Button disabled={job.date_exported || !jobRO}>{t("jobs.actions.sendtodms")}</Button>
|
<Button disabled={job.date_exported || !jobRO}>{t("jobs.actions.sendtodms")}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setPrintCenterContext({
|
||||||
|
context: {
|
||||||
|
id: job.id,
|
||||||
|
job: job,
|
||||||
|
type: "job"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
key="printing"
|
||||||
|
icon={<PrinterFilled />}
|
||||||
|
>
|
||||||
|
{t("jobs.actions.printCenter")}
|
||||||
|
</Button>
|
||||||
<JobsScoreboardAdd job={job} disabled={false} />
|
<JobsScoreboardAdd job={job} disabled={false} />
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { useNotification } from "../../contexts/Notifications/notificationContex
|
|||||||
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||||
|
|
||||||
const CardPaymentModalContainer = lazy(
|
const CardPaymentModalContainer = lazy(
|
||||||
() => import("../../components/card-payment-modal/card-payment-modal.container.")
|
() => import("../../components/card-payment-modal/card-payment-modal.container.jsx")
|
||||||
);
|
);
|
||||||
|
|
||||||
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container"));
|
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container"));
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ export function AllTasksPageContainer({ setBreadcrumbs, setSelectedHeader, setTa
|
|||||||
if (taskId) {
|
if (taskId) {
|
||||||
setTaskUpsertContext({
|
setTaskUpsertContext({
|
||||||
context: {
|
context: {
|
||||||
taskId
|
taskId,
|
||||||
|
view: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
urlParams.delete("taskid");
|
urlParams.delete("taskid");
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
updateCurrentUser
|
updateCurrentUser
|
||||||
} from "../../firebase/firebase.utils";
|
} from "../../firebase/firebase.utils";
|
||||||
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
|
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
|
||||||
|
import cleanAxios from "../../utils/CleanAxios";
|
||||||
import client from "../../utils/GraphQLClient";
|
import client from "../../utils/GraphQLClient";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
@@ -47,7 +48,6 @@ import {
|
|||||||
validatePasswordResetSuccess
|
validatePasswordResetSuccess
|
||||||
} from "./user.actions";
|
} from "./user.actions";
|
||||||
import UserActionTypes from "./user.types";
|
import UserActionTypes from "./user.types";
|
||||||
import cleanAxios from "../../utils/CleanAxios";
|
|
||||||
|
|
||||||
const fpPromise = FingerprintJS.load();
|
const fpPromise = FingerprintJS.load();
|
||||||
|
|
||||||
@@ -234,16 +234,23 @@ export function* signInSuccessSaga({ payload }) {
|
|||||||
LogRocket.identify(payload.email);
|
LogRocket.identify(payload.email);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
|
||||||
|
const currentUserSegment = InstanceRenderManager({
|
||||||
|
imex: "imex-online-user",
|
||||||
|
rome: "rome-online-user"
|
||||||
|
});
|
||||||
|
window.$crisp.push(["set", "session:segments", [[currentUserSegment]]]);
|
||||||
|
|
||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
executeFunction: true,
|
executeFunction: true,
|
||||||
args: [],
|
args: [],
|
||||||
imex: () => {
|
imex: () => {
|
||||||
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
|
window.$crisp.push(["set", "session:segments", [["imex"]]]);
|
||||||
window.$crisp.push(["set", "session:segments", [["user"]]]);
|
|
||||||
},
|
},
|
||||||
rome: () => {
|
rome: () => {
|
||||||
window.$zoho.salesiq.visitor.name(payload.displayName || payload.email);
|
window.$zoho.salesiq.visitor.name(payload.displayName || payload.email);
|
||||||
window.$zoho.salesiq.visitor.email(payload.email);
|
window.$zoho.salesiq.visitor.email(payload.email);
|
||||||
|
window.$crisp.push(["set", "session:segments", [["rome"]]]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -380,11 +380,11 @@
|
|||||||
"md_parts_order_comment": "Parts Orders Comments",
|
"md_parts_order_comment": "Parts Orders Comments",
|
||||||
"md_parts_scan": {
|
"md_parts_scan": {
|
||||||
"caseInsensitive": "Case Insensitive",
|
"caseInsensitive": "Case Insensitive",
|
||||||
"expression": "",
|
"expression": "",
|
||||||
"field": "Field",
|
"field": "Field",
|
||||||
"flags": "",
|
"flags": "",
|
||||||
"operation": "Operation",
|
"operation": "Operation",
|
||||||
"value": "Value"
|
"value": "Value"
|
||||||
},
|
},
|
||||||
"md_payment_types": "Payment Types",
|
"md_payment_types": "Payment Types",
|
||||||
"md_referral_sources": "Referral Sources",
|
"md_referral_sources": "Referral Sources",
|
||||||
@@ -712,18 +712,19 @@
|
|||||||
"ssbuckets": "Job Size Definitions",
|
"ssbuckets": "Job Size Definitions",
|
||||||
"systemsettings": "System Settings",
|
"systemsettings": "System Settings",
|
||||||
"task-presets": "Task Presets",
|
"task-presets": "Task Presets",
|
||||||
"workingdays": "Working Days"
|
"workingdays": "Working Days",
|
||||||
|
"shop_enabled_features": "Shop Enabled Features"
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"contains": "Contains",
|
"contains": "Contains",
|
||||||
"ends_with": "Ends With",
|
"ends_with": "Ends With",
|
||||||
"equals": "Equals",
|
"equals": "Equals",
|
||||||
"greater_than": "Greater Than",
|
"greater_than": "Greater Than",
|
||||||
"less_than": "Less Than",
|
"less_than": "Less Than",
|
||||||
"not_equals": "Not Equals",
|
"not_equals": "Not Equals",
|
||||||
"starts_with": "Starts With"
|
"starts_with": "Starts With"
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"areyousure": "Are you sure you want to continue?",
|
"areyousure": "Are you sure you want to continue?",
|
||||||
"defaultviewcreated": "Default view created successfully.",
|
"defaultviewcreated": "Default view created successfully.",
|
||||||
"save": "Shop configuration saved successfully. ",
|
"save": "Shop configuration saved successfully. ",
|
||||||
@@ -1193,7 +1194,9 @@
|
|||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"tryagain": "Try Again",
|
"tryagain": "Try Again",
|
||||||
"view": "View",
|
"view": "View",
|
||||||
"viewreleasenotes": "See What's Changed"
|
"viewreleasenotes": "See What's Changed",
|
||||||
|
"sharetoteams": "Share to Teams",
|
||||||
|
"ok": "Ok"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
||||||
@@ -1437,13 +1440,13 @@
|
|||||||
"adjustment": "Adjustment",
|
"adjustment": "Adjustment",
|
||||||
"ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)",
|
"ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)",
|
||||||
"alt_partno": "Alt Part #",
|
"alt_partno": "Alt Part #",
|
||||||
"amount": "Amount",
|
"amount": "Amount",
|
||||||
"assigned_team": "Team",
|
"assigned_team": "Team",
|
||||||
"assigned_team_name": "Team {{name}}",
|
"assigned_team_name": "Team {{name}}",
|
||||||
"create_ppc": "Create PPC?",
|
"create_ppc": "Create PPC?",
|
||||||
"db_price": "List Price",
|
"db_price": "List Price",
|
||||||
"include_in_part_cnt": "Include in Parts Status Count",
|
"include_in_part_cnt": "Include in Parts Status Count",
|
||||||
"lbr_types": {
|
"lbr_types": {
|
||||||
"LA1": "LA1",
|
"LA1": "LA1",
|
||||||
"LA2": "LA2",
|
"LA2": "LA2",
|
||||||
"LA3": "LA3",
|
"LA3": "LA3",
|
||||||
@@ -1525,7 +1528,7 @@
|
|||||||
"addDocuments": "Add Job Documents",
|
"addDocuments": "Add Job Documents",
|
||||||
"addNote": "Add Note",
|
"addNote": "Add Note",
|
||||||
"addtopartsqueue": "Add to Parts Queue",
|
"addtopartsqueue": "Add to Parts Queue",
|
||||||
"addtoproduction": "Add In Production Flag",
|
"addtoproduction": "Add to Production",
|
||||||
"addtoscoreboard": "Add to Scoreboard",
|
"addtoscoreboard": "Add to Scoreboard",
|
||||||
"allocate": "Allocate",
|
"allocate": "Allocate",
|
||||||
"autoallocate": "Auto Allocate",
|
"autoallocate": "Auto Allocate",
|
||||||
@@ -1568,7 +1571,7 @@
|
|||||||
"printCenter": "Print Center",
|
"printCenter": "Print Center",
|
||||||
"recalculate": "Recalculate",
|
"recalculate": "Recalculate",
|
||||||
"reconcile": "Reconcile",
|
"reconcile": "Reconcile",
|
||||||
"removefromproduction": "Remove In Production Flag",
|
"removefromproduction": "Remove from Production",
|
||||||
"schedule": "Schedule",
|
"schedule": "Schedule",
|
||||||
"sendcsi": "Send CSI",
|
"sendcsi": "Send CSI",
|
||||||
"sendpartspricechange": "Send Parts Price Change",
|
"sendpartspricechange": "Send Parts Price Change",
|
||||||
@@ -2857,7 +2860,8 @@
|
|||||||
"touchtime": "T/T",
|
"touchtime": "T/T",
|
||||||
"vertical": "Vertical",
|
"vertical": "Vertical",
|
||||||
"viewname": "View Name",
|
"viewname": "View Name",
|
||||||
"wide": "Wide"
|
"wide": "Wide",
|
||||||
|
"unassigned": "Unassigned"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"horizontal": "Horizontal",
|
"horizontal": "Horizontal",
|
||||||
@@ -3071,7 +3075,7 @@
|
|||||||
"production_by_repair_status": "Production by Status",
|
"production_by_repair_status": "Production by Status",
|
||||||
"production_by_repair_status_one": "Production filtered by Status",
|
"production_by_repair_status_one": "Production filtered by Status",
|
||||||
"production_by_ro": "Production by RO",
|
"production_by_ro": "Production by RO",
|
||||||
"production_by_target_date": "Production by Target Date",
|
"production_by_target_date": "Production by Scheduled Completion",
|
||||||
"production_by_technician": "Production by Technician",
|
"production_by_technician": "Production by Technician",
|
||||||
"production_by_technician_one": "Production filtered by Technician",
|
"production_by_technician_one": "Production filtered by Technician",
|
||||||
"production_not_production_status": "Production not in Production Status",
|
"production_not_production_status": "Production not in Production Status",
|
||||||
@@ -3081,7 +3085,8 @@
|
|||||||
"purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)",
|
"purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)",
|
||||||
"purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)",
|
"purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)",
|
||||||
"purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)",
|
"purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)",
|
||||||
"purchases_by_cost_center_summary": "Purchases by Cost Center (Summary)","purchases_by_date_excel": "Purchases by Date - Excel",
|
"purchases_by_cost_center_summary": "Purchases by Cost Center (Summary)",
|
||||||
|
"purchases_by_date_excel": "Purchases by Date - Excel",
|
||||||
"purchases_by_date_range_detail": "Purchases by Date - Detail",
|
"purchases_by_date_range_detail": "Purchases by Date - Detail",
|
||||||
"purchases_by_date_range_summary": "Purchases by Date - Summary",
|
"purchases_by_date_range_summary": "Purchases by Date - Summary",
|
||||||
"purchases_by_ro_detail_date": "Purchases by RO - Detail",
|
"purchases_by_ro_detail_date": "Purchases by RO - Detail",
|
||||||
@@ -3182,7 +3187,8 @@
|
|||||||
"tasks": {
|
"tasks": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"edit": "Edit Task",
|
"edit": "Edit Task",
|
||||||
"new": "New Task"
|
"new": "New Task",
|
||||||
|
"view": "View Task"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"allTasks": "All",
|
"allTasks": "All",
|
||||||
|
|||||||
@@ -380,11 +380,11 @@
|
|||||||
"md_parts_order_comment": "",
|
"md_parts_order_comment": "",
|
||||||
"md_parts_scan": {
|
"md_parts_scan": {
|
||||||
"caseInsensitive": "",
|
"caseInsensitive": "",
|
||||||
"expression": "",
|
"expression": "",
|
||||||
"field": "",
|
"field": "",
|
||||||
"flags": "",
|
"flags": "",
|
||||||
"operation": "",
|
"operation": "",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
"md_payment_types": "",
|
"md_payment_types": "",
|
||||||
"md_referral_sources": "",
|
"md_referral_sources": "",
|
||||||
@@ -712,18 +712,19 @@
|
|||||||
"ssbuckets": "",
|
"ssbuckets": "",
|
||||||
"systemsettings": "",
|
"systemsettings": "",
|
||||||
"task-presets": "",
|
"task-presets": "",
|
||||||
"workingdays": ""
|
"workingdays": "",
|
||||||
|
"shop_enabled_features": ""
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"contains": "",
|
"contains": "",
|
||||||
"ends_with": "",
|
"ends_with": "",
|
||||||
"equals": "",
|
"equals": "",
|
||||||
"greater_than": "",
|
"greater_than": "",
|
||||||
"less_than": "",
|
"less_than": "",
|
||||||
"not_equals": "",
|
"not_equals": "",
|
||||||
"starts_with": ""
|
"starts_with": ""
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"areyousure": "",
|
"areyousure": "",
|
||||||
"defaultviewcreated": "",
|
"defaultviewcreated": "",
|
||||||
"save": "",
|
"save": "",
|
||||||
@@ -1193,7 +1194,9 @@
|
|||||||
"submit": "",
|
"submit": "",
|
||||||
"tryagain": "",
|
"tryagain": "",
|
||||||
"view": "",
|
"view": "",
|
||||||
"viewreleasenotes": ""
|
"viewreleasenotes": "",
|
||||||
|
"sharetoteams": "",
|
||||||
|
"ok": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "",
|
"fcm": "",
|
||||||
@@ -1437,13 +1440,13 @@
|
|||||||
"adjustment": "",
|
"adjustment": "",
|
||||||
"ah_detail_line": "",
|
"ah_detail_line": "",
|
||||||
"alt_partno": "",
|
"alt_partno": "",
|
||||||
"amount": "",
|
"amount": "",
|
||||||
"assigned_team": "",
|
"assigned_team": "",
|
||||||
"assigned_team_name": "",
|
"assigned_team_name": "",
|
||||||
"create_ppc": "",
|
"create_ppc": "",
|
||||||
"db_price": "Precio de base de datos",
|
"db_price": "Precio de base de datos",
|
||||||
"include_in_part_cnt": "",
|
"include_in_part_cnt": "",
|
||||||
"lbr_types": {
|
"lbr_types": {
|
||||||
"LA1": "",
|
"LA1": "",
|
||||||
"LA2": "",
|
"LA2": "",
|
||||||
"LA3": "",
|
"LA3": "",
|
||||||
@@ -2857,7 +2860,8 @@
|
|||||||
"touchtime": "",
|
"touchtime": "",
|
||||||
"vertical": "",
|
"vertical": "",
|
||||||
"viewname": "",
|
"viewname": "",
|
||||||
"wide": ""
|
"wide": "",
|
||||||
|
"unassigned": ""
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"horizontal": "",
|
"horizontal": "",
|
||||||
@@ -3082,7 +3086,7 @@
|
|||||||
"purchase_return_ratio_grouped_by_vendor_summary": "",
|
"purchase_return_ratio_grouped_by_vendor_summary": "",
|
||||||
"purchases_by_cost_center_detail": "",
|
"purchases_by_cost_center_detail": "",
|
||||||
"purchases_by_cost_center_summary": "",
|
"purchases_by_cost_center_summary": "",
|
||||||
"purchases_by_date_excel": "",
|
"purchases_by_date_excel": "",
|
||||||
"purchases_by_date_range_detail": "",
|
"purchases_by_date_range_detail": "",
|
||||||
"purchases_by_date_range_summary": "",
|
"purchases_by_date_range_summary": "",
|
||||||
"purchases_by_ro_detail_date": "",
|
"purchases_by_ro_detail_date": "",
|
||||||
@@ -3183,7 +3187,8 @@
|
|||||||
"tasks": {
|
"tasks": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"edit": "",
|
"edit": "",
|
||||||
"new": ""
|
"new": "",
|
||||||
|
"view": ""
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"allTasks": "",
|
"allTasks": "",
|
||||||
|
|||||||
@@ -380,11 +380,11 @@
|
|||||||
"md_parts_order_comment": "",
|
"md_parts_order_comment": "",
|
||||||
"md_parts_scan": {
|
"md_parts_scan": {
|
||||||
"caseInsensitive": "",
|
"caseInsensitive": "",
|
||||||
"expression": "",
|
"expression": "",
|
||||||
"field": "",
|
"field": "",
|
||||||
"flags": "",
|
"flags": "",
|
||||||
"operation": "",
|
"operation": "",
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
"md_payment_types": "",
|
"md_payment_types": "",
|
||||||
"md_referral_sources": "",
|
"md_referral_sources": "",
|
||||||
@@ -712,18 +712,19 @@
|
|||||||
"ssbuckets": "",
|
"ssbuckets": "",
|
||||||
"systemsettings": "",
|
"systemsettings": "",
|
||||||
"task-presets": "",
|
"task-presets": "",
|
||||||
"workingdays": ""
|
"workingdays": "",
|
||||||
|
"shop_enabled_features": ""
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"contains": "",
|
"contains": "",
|
||||||
"ends_with": "",
|
"ends_with": "",
|
||||||
"equals": "",
|
"equals": "",
|
||||||
"greater_than": "",
|
"greater_than": "",
|
||||||
"less_than": "",
|
"less_than": "",
|
||||||
"not_equals": "",
|
"not_equals": "",
|
||||||
"starts_with": ""
|
"starts_with": ""
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"areyousure": "",
|
"areyousure": "",
|
||||||
"defaultviewcreated": "",
|
"defaultviewcreated": "",
|
||||||
"save": "",
|
"save": "",
|
||||||
@@ -1193,7 +1194,9 @@
|
|||||||
"submit": "",
|
"submit": "",
|
||||||
"tryagain": "",
|
"tryagain": "",
|
||||||
"view": "",
|
"view": "",
|
||||||
"viewreleasenotes": ""
|
"viewreleasenotes": "",
|
||||||
|
"sharetoteams": "",
|
||||||
|
"ok": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "",
|
"fcm": "",
|
||||||
@@ -1437,13 +1440,13 @@
|
|||||||
"adjustment": "",
|
"adjustment": "",
|
||||||
"ah_detail_line": "",
|
"ah_detail_line": "",
|
||||||
"alt_partno": "",
|
"alt_partno": "",
|
||||||
"amount": "",
|
"amount": "",
|
||||||
"assigned_team": "",
|
"assigned_team": "",
|
||||||
"assigned_team_name": "",
|
"assigned_team_name": "",
|
||||||
"create_ppc": "",
|
"create_ppc": "",
|
||||||
"db_price": "Prix de la base de données",
|
"db_price": "Prix de la base de données",
|
||||||
"include_in_part_cnt": "",
|
"include_in_part_cnt": "",
|
||||||
"lbr_types": {
|
"lbr_types": {
|
||||||
"LA1": "",
|
"LA1": "",
|
||||||
"LA2": "",
|
"LA2": "",
|
||||||
"LA3": "",
|
"LA3": "",
|
||||||
@@ -2857,7 +2860,8 @@
|
|||||||
"touchtime": "",
|
"touchtime": "",
|
||||||
"vertical": "",
|
"vertical": "",
|
||||||
"viewname": "",
|
"viewname": "",
|
||||||
"wide": ""
|
"wide": "",
|
||||||
|
"unassigned": ""
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"horizontal": "",
|
"horizontal": "",
|
||||||
@@ -3082,7 +3086,7 @@
|
|||||||
"purchase_return_ratio_grouped_by_vendor_summary": "",
|
"purchase_return_ratio_grouped_by_vendor_summary": "",
|
||||||
"purchases_by_cost_center_detail": "",
|
"purchases_by_cost_center_detail": "",
|
||||||
"purchases_by_cost_center_summary": "",
|
"purchases_by_cost_center_summary": "",
|
||||||
"purchases_by_date_excel": "",
|
"purchases_by_date_excel": "",
|
||||||
"purchases_by_date_range_detail": "",
|
"purchases_by_date_range_detail": "",
|
||||||
"purchases_by_date_range_summary": "",
|
"purchases_by_date_range_summary": "",
|
||||||
"purchases_by_ro_detail_date": "",
|
"purchases_by_ro_detail_date": "",
|
||||||
@@ -3183,7 +3187,8 @@
|
|||||||
"tasks": {
|
"tasks": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"edit": "",
|
"edit": "",
|
||||||
"new": ""
|
"new": "",
|
||||||
|
"view": ""
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
"allTasks": "",
|
"allTasks": "",
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import client from "../utils/GraphQLClient";
|
|||||||
import cleanAxios from "./CleanAxios";
|
import cleanAxios from "./CleanAxios";
|
||||||
import { TemplateList } from "./TemplateConstants";
|
import { TemplateList } from "./TemplateConstants";
|
||||||
import { generateTemplate } from "./graphQLmodifier";
|
import { generateTemplate } from "./graphQLmodifier";
|
||||||
import InstanceRenderManager from "./instanceRenderMgr";
|
|
||||||
|
|
||||||
const server = import.meta.env.VITE_APP_REPORTS_SERVER_URL;
|
const server = import.meta.env.VITE_APP_REPORTS_SERVER_URL;
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ export default async function RenderTemplate(
|
|||||||
jsreport.headers["Authorization"] = jsrAuth;
|
jsreport.headers["Authorization"] = jsrAuth;
|
||||||
|
|
||||||
//Query assets that match the template name. Must be in format <<templateName>>.query
|
//Query assets that match the template name. Must be in format <<templateName>>.query
|
||||||
let { contextData, useShopSpecificTemplate } = await fetchContextData(templateObject, jsrAuth);
|
let { contextData, useShopSpecificTemplate, shopSpecificFolder } = await fetchContextData(templateObject, jsrAuth);
|
||||||
|
|
||||||
const { ignoreCustomMargins } = Templates[templateObject.name];
|
const { ignoreCustomMargins } = Templates[templateObject.name];
|
||||||
|
|
||||||
@@ -74,14 +73,8 @@ export default async function RenderTemplate(
|
|||||||
...contextData,
|
...contextData,
|
||||||
...templateObject.variables,
|
...templateObject.variables,
|
||||||
...templateObject.context,
|
...templateObject.context,
|
||||||
headerpath: `/${InstanceRenderManager({
|
headerpath: shopSpecificFolder ? `/${bodyshop.imexshopid}/header.html` : `/GENERIC/header.html`,
|
||||||
imex: bodyshop.imexshopid,
|
footerpath: shopSpecificFolder ? `/${bodyshop.imexshopid}/footer.html` : `/GENERIC/footer.html`,
|
||||||
rome: bodyshop.imexshopid
|
|
||||||
})}/header.html`,
|
|
||||||
footerpath: `/${InstanceRenderManager({
|
|
||||||
imex: bodyshop.imexshopid,
|
|
||||||
rome: bodyshop.imexshopid
|
|
||||||
})}/footer.html`,
|
|
||||||
bodyshop: bodyshop,
|
bodyshop: bodyshop,
|
||||||
filters: templateObject?.filters,
|
filters: templateObject?.filters,
|
||||||
sorters: templateObject?.sorters,
|
sorters: templateObject?.sorters,
|
||||||
@@ -149,11 +142,12 @@ export async function RenderTemplates(templateObjects, bodyshop, renderAsHtml =
|
|||||||
templateObjects.forEach((template) => {
|
templateObjects.forEach((template) => {
|
||||||
proms.push(
|
proms.push(
|
||||||
(async () => {
|
(async () => {
|
||||||
let { contextData, useShopSpecificTemplate } = await fetchContextData(template, jsrAuth);
|
let { contextData, useShopSpecificTemplate, shopSpecificFolder } = await fetchContextData(template, jsrAuth);
|
||||||
unsortedTemplatesAndData.push({
|
unsortedTemplatesAndData.push({
|
||||||
templateObject: template,
|
templateObject: template,
|
||||||
contextData,
|
contextData,
|
||||||
useShopSpecificTemplate
|
useShopSpecificTemplate,
|
||||||
|
shopSpecificFolder
|
||||||
});
|
});
|
||||||
})()
|
})()
|
||||||
);
|
);
|
||||||
@@ -248,8 +242,8 @@ export async function RenderTemplates(templateObjects, bodyshop, renderAsHtml =
|
|||||||
|
|
||||||
// ...rootTemplate.templateObject.variables,
|
// ...rootTemplate.templateObject.variables,
|
||||||
// ...rootTemplate.templateObject.context,
|
// ...rootTemplate.templateObject.context,
|
||||||
headerpath: `/${bodyshop.imexshopid}/header.html`,
|
headerpath: rootTemplate.shopSpecificFolder ? `/${bodyshop.imexshopid}/header.html` : `/GENERIC/header.html`,
|
||||||
footerpath: `/${bodyshop.imexshopid}/footer.html`,
|
footerpath: rootTemplate.shopSpecificFolder ? `/${bodyshop.imexshopid}/footer.html` : `/GENERIC/footer.html`,
|
||||||
bodyshop: bodyshop,
|
bodyshop: bodyshop,
|
||||||
offset: bodyshop.timezone
|
offset: bodyshop.timezone
|
||||||
}
|
}
|
||||||
@@ -397,10 +391,10 @@ const fetchContextData = async (templateObject, jsrAuth) => {
|
|||||||
});
|
});
|
||||||
contextData = data;
|
contextData = data;
|
||||||
}
|
}
|
||||||
return { contextData, useShopSpecificTemplate };
|
return { contextData, useShopSpecificTemplate, shopSpecificFolder };
|
||||||
}
|
}
|
||||||
|
|
||||||
return await generateTemplate(templateQueryToExecute, templateObject, useShopSpecificTemplate);
|
return await generateTemplate(templateQueryToExecute, templateObject, useShopSpecificTemplate, shopSpecificFolder);
|
||||||
};
|
};
|
||||||
|
|
||||||
//export const displayTemplateInWindow = (html) => {
|
//export const displayTemplateInWindow = (html) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { gql } from "@apollo/client";
|
||||||
import { Kind, parse, print, visit } from "graphql";
|
import { Kind, parse, print, visit } from "graphql";
|
||||||
import client from "./GraphQLClient";
|
import client from "./GraphQLClient";
|
||||||
import { gql } from "@apollo/client";
|
|
||||||
|
|
||||||
/* eslint-disable no-loop-func */
|
/* eslint-disable no-loop-func */
|
||||||
|
|
||||||
@@ -114,9 +114,10 @@ export function printQuery(query) {
|
|||||||
* @param templateQueryToExecute
|
* @param templateQueryToExecute
|
||||||
* @param templateObject
|
* @param templateObject
|
||||||
* @param useShopSpecificTemplate
|
* @param useShopSpecificTemplate
|
||||||
* @returns {Promise<{contextData: {}, useShopSpecificTemplate}>}
|
* @param shopSpecificTemplate
|
||||||
|
* @returns {Promise<{contextData: {}, useShopSpecificTemplate, shopSpecificTemplate}>}
|
||||||
*/
|
*/
|
||||||
export async function generateTemplate(templateQueryToExecute, templateObject, useShopSpecificTemplate) {
|
export async function generateTemplate(templateQueryToExecute, templateObject, useShopSpecificTemplate, shopSpecificFolder) {
|
||||||
// Advanced Filtering and Sorting modifications start here
|
// Advanced Filtering and Sorting modifications start here
|
||||||
|
|
||||||
// Parse the query and apply the filters and sorters
|
// Parse the query and apply the filters and sorters
|
||||||
@@ -147,7 +148,7 @@ export async function generateTemplate(templateQueryToExecute, templateObject, u
|
|||||||
contextData = data;
|
contextData = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { contextData, useShopSpecificTemplate };
|
return { contextData, useShopSpecificTemplate, shopSpecificFolder };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import eslint from "vite-plugin-eslint";
|
|||||||
import { VitePWA } from "vite-plugin-pwa";
|
import { VitePWA } from "vite-plugin-pwa";
|
||||||
import InstanceRenderManager from "./src/utils/instanceRenderMgr";
|
import InstanceRenderManager from "./src/utils/instanceRenderMgr";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
//import { visualizer } from "rollup-plugin-visualizer";
|
|
||||||
|
|
||||||
|
// Ensure your environment variables are set correctly for Vite 6
|
||||||
process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
|
process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
|
||||||
timeZone: "America/Los_Angeles"
|
timeZone: "America/Los_Angeles"
|
||||||
});
|
});
|
||||||
@@ -22,7 +22,7 @@ export const logger = createLogger("info", {
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
base: "/",
|
base: "/",
|
||||||
plugins: [
|
plugins: [
|
||||||
//visualizer(),
|
// Ensure all plugins are Vite 6 compatible
|
||||||
ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
|
ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
injectRegister: "auto",
|
injectRegister: "auto",
|
||||||
@@ -31,14 +31,12 @@ export default defineConfig({
|
|||||||
short_name: InstanceRenderManager({
|
short_name: InstanceRenderManager({
|
||||||
instance: process.env.VITE_APP_INSTANCE,
|
instance: process.env.VITE_APP_INSTANCE,
|
||||||
imex: "ImEX Online",
|
imex: "ImEX Online",
|
||||||
rome: "Rome Online",
|
rome: "Rome Online"
|
||||||
|
|
||||||
}),
|
}),
|
||||||
name: InstanceRenderManager({
|
name: InstanceRenderManager({
|
||||||
instance: process.env.VITE_APP_INSTANCE,
|
instance: process.env.VITE_APP_INSTANCE,
|
||||||
imex: "ImEX Online",
|
imex: "ImEX Online",
|
||||||
rome: "Rome Online",
|
rome: "Rome Online"
|
||||||
|
|
||||||
}),
|
}),
|
||||||
description: "The ultimate bodyshop management system.",
|
description: "The ultimate bodyshop management system.",
|
||||||
icons: [
|
icons: [
|
||||||
@@ -46,7 +44,7 @@ export default defineConfig({
|
|||||||
src: InstanceRenderManager({
|
src: InstanceRenderManager({
|
||||||
instance: process.env.VITE_APP_INSTANCE,
|
instance: process.env.VITE_APP_INSTANCE,
|
||||||
imex: "favicon.png",
|
imex: "favicon.png",
|
||||||
rome: "ro-favicon.png",
|
rome: "ro-favicon.png"
|
||||||
}),
|
}),
|
||||||
sizes: "64x64 32x32 24x24 16x16",
|
sizes: "64x64 32x32 24x24 16x16",
|
||||||
type: "image/x-icon"
|
type: "image/x-icon"
|
||||||
@@ -55,7 +53,7 @@ export default defineConfig({
|
|||||||
src: InstanceRenderManager({
|
src: InstanceRenderManager({
|
||||||
instance: process.env.VITE_APP_INSTANCE,
|
instance: process.env.VITE_APP_INSTANCE,
|
||||||
imex: "logo192.png",
|
imex: "logo192.png",
|
||||||
rome: "logo192.png",
|
rome: "logo192.png"
|
||||||
}),
|
}),
|
||||||
type: "image/png",
|
type: "image/png",
|
||||||
sizes: "192x192"
|
sizes: "192x192"
|
||||||
@@ -64,7 +62,7 @@ export default defineConfig({
|
|||||||
src: InstanceRenderManager({
|
src: InstanceRenderManager({
|
||||||
instance: process.env.VITE_APP_INSTANCE,
|
instance: process.env.VITE_APP_INSTANCE,
|
||||||
imex: "logo512.png",
|
imex: "logo512.png",
|
||||||
rome: "ro-favicon.png",
|
rome: "ro-favicon.png"
|
||||||
}),
|
}),
|
||||||
type: "image/png",
|
type: "image/png",
|
||||||
sizes: "512x512"
|
sizes: "512x512"
|
||||||
@@ -73,7 +71,7 @@ export default defineConfig({
|
|||||||
theme_color: InstanceRenderManager({
|
theme_color: InstanceRenderManager({
|
||||||
instance: process.env.VITE_APP_INSTANCE,
|
instance: process.env.VITE_APP_INSTANCE,
|
||||||
imex: "#1890ff",
|
imex: "#1890ff",
|
||||||
rome: "#fff",
|
rome: "#fff"
|
||||||
}),
|
}),
|
||||||
background_color: "#fff",
|
background_color: "#fff",
|
||||||
gcm_sender_id: "103953800507"
|
gcm_sender_id: "103953800507"
|
||||||
@@ -204,8 +202,10 @@ export default defineConfig({
|
|||||||
"react-redux"
|
"react-redux"
|
||||||
],
|
],
|
||||||
esbuildOptions: {
|
esbuildOptions: {
|
||||||
|
// Update for Vite 6: Use proper file extensions
|
||||||
loader: {
|
loader: {
|
||||||
".js": "jsx"
|
".jsx": "jsx",
|
||||||
|
".tsx": "tsx"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,3 +39,11 @@
|
|||||||
headers:
|
headers:
|
||||||
- name: event-secret
|
- name: event-secret
|
||||||
value_from_env: EVENT_SECRET
|
value_from_env: EVENT_SECRET
|
||||||
|
- name: Rome Usage Report
|
||||||
|
webhook: '{{HASURA_API_URL}}/data/usagereport'
|
||||||
|
schedule: 0 12 * * 5
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: x-imex-auth
|
||||||
|
value_from_env: DATAPUMP_AUTH
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."associations" alter column "notification_settings" set default jsonb_build_object();
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table "public"."associations" alter column "notification_settings" set default jsonb_build_object();
|
||||||
3052
package-lock.json
generated
3052
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@@ -19,12 +19,12 @@
|
|||||||
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
|
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-cloudwatch-logs": "^3.693.0",
|
"@aws-sdk/client-cloudwatch-logs": "^3.738.0",
|
||||||
"@aws-sdk/client-elasticache": "^3.693.0",
|
"@aws-sdk/client-elasticache": "^3.738.0",
|
||||||
"@aws-sdk/client-s3": "^3.693.0",
|
"@aws-sdk/client-s3": "^3.738.0",
|
||||||
"@aws-sdk/client-secrets-manager": "^3.693.0",
|
"@aws-sdk/client-secrets-manager": "^3.738.0",
|
||||||
"@aws-sdk/client-ses": "^3.693.0",
|
"@aws-sdk/client-ses": "^3.738.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.693.0",
|
"@aws-sdk/credential-provider-node": "^3.738.0",
|
||||||
"@opensearch-project/opensearch": "^2.13.0",
|
"@opensearch-project/opensearch": "^2.13.0",
|
||||||
"@socket.io/admin-ui": "^0.5.1",
|
"@socket.io/admin-ui": "^0.5.1",
|
||||||
"@socket.io/redis-adapter": "^8.3.0",
|
"@socket.io/redis-adapter": "^8.3.0",
|
||||||
@@ -33,7 +33,6 @@
|
|||||||
"better-queue": "^3.8.12",
|
"better-queue": "^3.8.12",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"body-parser": "^1.20.3",
|
"body-parser": "^1.20.3",
|
||||||
"canvas": "^2.11.2",
|
|
||||||
"chart.js": "^4.4.6",
|
"chart.js": "^4.4.6",
|
||||||
"cloudinary": "^2.5.1",
|
"cloudinary": "^2.5.1",
|
||||||
"compression": "^1.7.5",
|
"compression": "^1.7.5",
|
||||||
@@ -41,31 +40,31 @@
|
|||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"crisp-status-reporter": "^1.2.2",
|
"crisp-status-reporter": "^1.2.2",
|
||||||
"csrf": "^3.1.0",
|
"csrf": "^3.1.0",
|
||||||
"dd-trace": "^5.28.0",
|
"dd-trace": "^5.33.1",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"firebase-admin": "^13.0.0",
|
"firebase-admin": "^13.0.2",
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.10.0",
|
||||||
"graphql-request": "^6.1.0",
|
"graphql-request": "^6.1.0",
|
||||||
"inline-css": "^4.0.2",
|
"inline-css": "^4.0.3",
|
||||||
"intuit-oauth": "^4.1.3",
|
"intuit-oauth": "^4.1.3",
|
||||||
"ioredis": "^5.4.1",
|
"ioredis": "^5.4.2",
|
||||||
"json-2-csv": "^5.5.6",
|
"json-2-csv": "^5.5.8",
|
||||||
"juice": "^11.0.0",
|
"juice": "^11.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"moment-timezone": "^0.5.46",
|
"moment-timezone": "^0.5.47",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"node-mailjet": "^6.0.6",
|
"node-mailjet": "^6.0.6",
|
||||||
"node-persist": "^4.0.3",
|
"node-persist": "^4.0.4",
|
||||||
"nodemailer": "^6.9.16",
|
"nodemailer": "^6.10.0",
|
||||||
"phone": "^3.1.53",
|
"phone": "^3.1.58",
|
||||||
"recursive-diff": "^1.0.9",
|
"recursive-diff": "^1.0.9",
|
||||||
"redis": "^4.7.0",
|
"redis": "^4.7.0",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"skia-canvas": "^2.0.0",
|
"skia-canvas": "^2.0.2",
|
||||||
"soap": "^1.1.6",
|
"soap": "^1.1.7",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"socket.io-adapter": "^2.5.5",
|
"socket.io-adapter": "^2.5.5",
|
||||||
"ssh2-sftp-client": "^11.0.0",
|
"ssh2-sftp-client": "^11.0.0",
|
||||||
@@ -77,12 +76,12 @@
|
|||||||
"xmlbuilder2": "^3.1.1"
|
"xmlbuilder2": "^3.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.15.0",
|
"@eslint/js": "^9.19.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"concurrently": "^8.2.2",
|
"concurrently": "^8.2.2",
|
||||||
"eslint": "^9.15.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-react": "^7.37.2",
|
"eslint-plugin-react": "^7.37.4",
|
||||||
"globals": "^15.12.0",
|
"globals": "^15.14.0",
|
||||||
"p-limit": "^3.1.0",
|
"p-limit": "^3.1.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"source-map-explorer": "^2.5.2"
|
"source-map-explorer": "^2.5.2"
|
||||||
|
|||||||
@@ -818,6 +818,7 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
PAG: Dinero(),
|
PAG: Dinero(),
|
||||||
PAO: Dinero(),
|
PAO: Dinero(),
|
||||||
PAS: Dinero(),
|
PAS: Dinero(),
|
||||||
|
PASL: Dinero(),
|
||||||
PAP: Dinero(),
|
PAP: Dinero(),
|
||||||
PAM: Dinero(),
|
PAM: Dinero(),
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
const { createCanvas } = require("canvas");
|
|
||||||
const { Canvas, FontLibrary } = require("skia-canvas");
|
const { Canvas, FontLibrary } = require("skia-canvas");
|
||||||
const Chart = require("chart.js/auto");
|
const Chart = require("chart.js/auto");
|
||||||
|
|
||||||
@@ -65,7 +64,7 @@ const getChartConfiguration = (keys, values, override) => {
|
|||||||
return defaultsDeep(override || {}, defaultConfiguration);
|
return defaultsDeep(override || {}, defaultConfiguration);
|
||||||
};
|
};
|
||||||
|
|
||||||
const processCanvasRequest = async (req, res, isSkia = false) => {
|
const processCanvasRequest = async (req, res) => {
|
||||||
const { logger } = req;
|
const { logger } = req;
|
||||||
const { w, h, values, keys, override } = req.body;
|
const { w, h, values, keys, override } = req.body;
|
||||||
|
|
||||||
@@ -77,7 +76,6 @@ const processCanvasRequest = async (req, res, isSkia = false) => {
|
|||||||
|
|
||||||
const configuration = getChartConfiguration(keys, values, override);
|
const configuration = getChartConfiguration(keys, values, override);
|
||||||
|
|
||||||
// Placeholders to allow fine control over GAC
|
|
||||||
let canvas = null;
|
let canvas = null;
|
||||||
let ctx = null;
|
let ctx = null;
|
||||||
let chart = null;
|
let chart = null;
|
||||||
@@ -85,16 +83,15 @@ const processCanvasRequest = async (req, res, isSkia = false) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Create the canvas
|
// Create the canvas
|
||||||
canvas = isSkia ? new Canvas(width, height) : createCanvas(width, height);
|
canvas = new Canvas(width, height);
|
||||||
ctx = canvas.getContext("2d");
|
ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
// Render the chart
|
// Render the chart
|
||||||
chart = new Chart(ctx, configuration);
|
chart = new Chart(ctx, configuration);
|
||||||
|
|
||||||
// Generate and send the image
|
// Generate and send the image
|
||||||
chartImage = isSkia ? (await canvas.toBuffer("image/png")).toString("base64") : canvas.toDataURL();
|
chartImage = (await canvas.toBuffer("image/png")).toString("base64");
|
||||||
|
res.status(200).send(`data:image/png;base64,${chartImage}`);
|
||||||
res.status(200).send(isSkia ? `data:image/png;base64,${chartImage}` : chartImage);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log the error and send the response
|
// Log the error and send the response
|
||||||
logger.log("canvas-error", "error", "jsr", null, { error: error.message });
|
logger.log("canvas-error", "error", "jsr", null, { error: error.message });
|
||||||
@@ -104,27 +101,27 @@ const processCanvasRequest = async (req, res, isSkia = false) => {
|
|||||||
if (chart) {
|
if (chart) {
|
||||||
chart.destroy();
|
chart.destroy();
|
||||||
}
|
}
|
||||||
ctx = null; // Explicitly nullify for garbage collection
|
ctx = null;
|
||||||
canvas = null; // Explicitly nullify for garbage collection
|
canvas = null;
|
||||||
chartImage = null;
|
chartImage = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const enqueueRequest = (req, res, isSkia) => {
|
const enqueueRequest = (req, res) => {
|
||||||
if (requestQueue.length >= CANVAS_QUEUE_LIMIT) {
|
if (requestQueue.length >= CANVAS_QUEUE_LIMIT) {
|
||||||
res.status(503).send("Server is busy. Please try again later.");
|
res.status(503).send("Server is busy. Please try again later.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
requestQueue.push({ req, res, isSkia });
|
requestQueue.push({ req, res });
|
||||||
req.logger.log("inbound-canvas-creation-queue", "debug", "jsr", null, { queue: requestQueue.length });
|
req.logger.log("inbound-canvas-creation-queue", "debug", "jsr", null, { queue: requestQueue.length });
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const processNextInQueue = async () => {
|
const processNextInQueue = async () => {
|
||||||
while (requestQueue.length > 0) {
|
while (requestQueue.length > 0) {
|
||||||
const { req, res, isSkia } = requestQueue.shift();
|
const { req, res } = requestQueue.shift();
|
||||||
try {
|
try {
|
||||||
await processCanvasRequest(req, res, isSkia);
|
await processCanvasRequest(req, res);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("canvas-queue-error", "error", "jsr", null, { error: err.message });
|
console.error("canvas-queue-error", "error", "jsr", null, { error: err.message });
|
||||||
}
|
}
|
||||||
@@ -137,13 +134,7 @@ exports.canvastest = function (req, res) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.canvas = async (req, res) => {
|
exports.canvas = async (req, res) => {
|
||||||
if (isProcessing || !enqueueRequest(req, res, false)) return;
|
if (isProcessing || !enqueueRequest(req, res)) return;
|
||||||
isProcessing = true;
|
|
||||||
processNextInQueue().catch((err) => console.error("canvas-processing-error", { error: err.message }));
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.canvasSkia = async (req, res) => {
|
|
||||||
if (isProcessing || !enqueueRequest(req, res, true)) return;
|
|
||||||
isProcessing = true;
|
isProcessing = true;
|
||||||
processNextInQueue().catch((err) => console.error("canvas-processing-error", { error: err.message }));
|
processNextInQueue().catch((err) => console.error("canvas-processing-error", { error: err.message }));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ const express = require("express");
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { inlinecss } = require("../render/inlinecss");
|
const { inlinecss } = require("../render/inlinecss");
|
||||||
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
const { canvas, canvasSkia } = require("../render/canvas-handler");
|
const { canvas } = require("../render/canvas-handler");
|
||||||
const validateCanvasInputMiddleware = require("../middleware/validateCanvasInputMiddleware");
|
const validateCanvasInputMiddleware = require("../middleware/validateCanvasInputMiddleware");
|
||||||
|
|
||||||
// Define the route for inline CSS rendering
|
// Define the route for inline CSS rendering
|
||||||
router.post("/inlinecss", validateFirebaseIdTokenMiddleware, inlinecss);
|
router.post("/inlinecss", validateFirebaseIdTokenMiddleware, inlinecss);
|
||||||
router.post("/canvas", [validateFirebaseIdTokenMiddleware, validateCanvasInputMiddleware], canvas);
|
router.post("/canvas-skia", validateFirebaseIdTokenMiddleware, validateCanvasInputMiddleware, canvas);
|
||||||
router.post("/canvas-skia", [validateFirebaseIdTokenMiddleware, validateCanvasInputMiddleware], canvasSkia);
|
router.post("/canvas", validateFirebaseIdTokenMiddleware, validateCanvasInputMiddleware, canvas);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user