Compare commits

...

29 Commits

Author SHA1 Message Date
Patrick Fic
40aca91c76 IO-3077 Move parts scan order to correctly update totals. 2025-02-11 10:51:26 -08:00
Dave Richer
abe4f4fb3d feature/IO-3077-import-rules-engine - Fix Translations 2025-02-11 12:34:28 -05:00
Patrick Fic
35a3726cf0 IO-3077 Implement import rules engine. 2025-02-10 14:24:50 -08:00
Patrick Fic
95a592fb9a Merged in feature/IO-3127-Dashboard-Schedule-Translations (pull request #2106)
IO-3127 Dashboard Schedule Translations

Approved-by: Dave Richer
2025-02-10 14:44:02 +00:00
Allan Carr
872e36a61a Merged in hotfix/2025-02-06 (pull request #2108)
IO-3121 Adjust Footer
2025-02-06 16:33:52 +00:00
Allan Carr
779f608506 Merged in feature/IO-3121-Generic-Report-Header (pull request #2107)
IO-3121 Adjust Footer
2025-02-06 16:32:50 +00:00
Allan Carr
d9f562faa4 IO-3121 Adjust Footer
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-02-06 08:32:52 -08:00
Patrick Fic
14e362ec3f Merged in release/2025-01-31 (pull request #2105)
Release/2025 01 31 - IO-1582, IO-2676, IO-2681, IO-2825, IO-2952, IO-2970, IO-3074, IO-3075, IO-3076, IO-3096, IO-3101, IO-3103, IO-3114, IO-3115, IO-3116, IO-3121, IO-3123
2025-02-06 04:01:53 +00:00
Patrick Fic
c213e13624 Resolve CI issues. 2025-02-05 20:00:48 -08:00
Patrick Fic
dae7642a8c Merged in release/2025-01-31 (pull request #2104)
Release/2025 01 31 - IO-1582, IO-2676, IO-2681, IO-2825, IO-2952, IO-2970, IO-3074, IO-3075, IO-3076, IO-3096, IO-3101, IO-3103, IO-3114, IO-3115, IO-3116, IO-3121, IO-3123
2025-02-06 03:56:56 +00:00
Allan Carr
c751f0cba4 IO-3127 Dashboard Schedule Translations
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-02-05 15:10:18 -08:00
Allan Carr
e128c108f8 Merged in feature/IO-3121-Generic-Report-Header (pull request #2102)
IO-3121 Generic Report Header

Approved-by: Dave Richer
2025-02-05 15:18:45 +00:00
Allan Carr
b8b76cb96c IO-3121 Generic Report Header
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-02-04 18:11:48 -08:00
Allan Carr
fa958cbbfe IO-3121 Generic Report Header
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-02-04 17:42:40 -08:00
Dave Richer
2146672916 release/2025-01-31 - fix teams icon 2025-02-04 13:47:58 -05:00
Dave Richer
f96460f332 Merged in feature/IO-2825-Node-22-Update (pull request #2092)
[DO NOT MERGE] Feature/IO-2825 Node 22 Update

Approved-by: Patrick Fic
2025-02-04 17:54:06 +00:00
Dave Richer
04c70876d0 Merged release/2025-01-31 into feature/IO-2825-Node-22-Update 2025-02-04 17:53:52 +00:00
Dave Richer
bc6a94eede Merged in feature/IO-3123-Code-Review-Adjustments (pull request #2099)
feature/IO-3123-Code-Review-Adjustments: Make Code review adjustments
2025-02-04 17:53:36 +00:00
Dave Richer
07b18836f5 feature/IO-2825-Node-22-Update: Fix Header styling 2025-02-03 16:11:14 -05:00
Dave Richer
ff08d19d79 Merged release/2025-01-31 into feature/IO-2825-Node-22-Update 2025-02-03 16:46:01 +00:00
Dave Richer
68584243f4 Merge remote-tracking branch 'origin/release/2025-01-31' into feature/IO-2825-Node-22-Update 2025-01-30 15:37:37 -05:00
Dave Richer
994d7e17aa feature/IO-2825-Node-22-Update - Package Updates 2025-01-30 15:36:05 -05:00
Dave Richer
fd1dd6dddd Merge remote-tracking branch 'origin/release/2025-01-31' into feature/IO-2825-Node-22-Update 2025-01-30 15:32:31 -05:00
Dave Richer
1e9b82ba1e feature/IO-2825-Node-22-Update - Merge release 2025-01-30 15:32:11 -05:00
Dave Richer
da41668b3f feature/IO-3096-Notification-Preferences - Update Circle CI to Node 22 2025-01-22 10:56:53 -08:00
Dave Richer
df008abec9 Merge remote-tracking branch 'origin/master-AIO' into feature/IO-3096-Global-Notification-Preferences 2025-01-22 10:53:42 -08:00
Dave Richer
29c99f2dd9 feature/IO-3096-Global-Notification-Preferences - Upgrade Node to 22 / Remove canvas and replace it 100% with canvas-skia 2025-01-20 09:02:55 -08:00
Dave Richer
3033e84f45 Merge remote-tracking branch 'origin/master-AIO' into feature/IO-3096-Global-Notification-Preferences 2025-01-20 08:33:31 -08:00
Dave Richer
18966476e4 feature/IO-3096-Global-Notifications-Preferences
-  Package Updates
2025-01-17 09:26:30 -08:00
23 changed files with 19210 additions and 16930 deletions

View File

@@ -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 2023"
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 running on 64bit Amazon Linux 2023"
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

View File

@@ -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

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1"> <babeledit_project be_version="2.7.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -6453,6 +6453,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>mark_critical</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>operation</name> <name>operation</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -6474,6 +6495,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>update_field</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>update_value</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>value</name> <name>value</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -11943,6 +12006,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>shop_enabled_features</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>shopinfo</name> <name>shopinfo</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -12312,6 +12396,37 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>tooltips</name>
<children>
<folder_node>
<name>md_parts_scan</name>
<children>
<concept_node>
<name>update_value_tooltip</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node> <folder_node>
<name>validation</name> <name>validation</name>
<children> <children>
@@ -19091,6 +19206,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>ok</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>previous</name> <name>previous</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -19385,6 +19521,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>sharetoteams</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>submit</name> <name>submit</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -43090,6 +43247,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>parts_returns</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>print</name> <name>print</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48557,6 +48735,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>unassigned</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>vertical</name> <name>vertical</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -52732,6 +52931,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>purchases_by_date_excel</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>purchases_by_date_range_detail</name> <name>purchases_by_date_range_detail</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -54483,6 +54703,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>

9684
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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"
} }
} }

View File

@@ -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;

View File

@@ -62,6 +62,9 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
refetchQueries: ["GET_LINE_TICKET_BY_PK"] refetchQueries: ["GET_LINE_TICKET_BY_PK"]
}); });
if (!r.errors) { if (!r.errors) {
if (CriticalPartsScanning.treatment === "on") {
await CriticalPartsScan(jobLineEditModal.context.jobid, notification);
}
await Axios.post("/job/totalsssu", { await Axios.post("/job/totalsssu", {
id: jobLineEditModal.context.jobid id: jobLineEditModal.context.jobid
}); });
@@ -107,7 +110,9 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
}) })
}); });
} }
if (CriticalPartsScanning.treatment === "on") {
await CriticalPartsScan(jobLineEditModal.context.jobid, notification);
}
if (jobLineEditModal.actions.submit) { if (jobLineEditModal.actions.submit) {
jobLineEditModal.actions.submit(); jobLineEditModal.actions.submit();
} else { } else {
@@ -115,9 +120,7 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
} }
toggleModalVisible(); toggleModalVisible();
} }
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(jobLineEditModal.context.jobid, notification);
}
setLoading(false); setLoading(false);
}; };

View File

@@ -172,13 +172,13 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
job: newJob job: newJob
} }
}); });
if (CriticalPartsScanning.treatment === "on") {
await CriticalPartsScan(r.data.insert_jobs.returning[0].id, notification);
}
await Axios.post("/job/totalsssu", { await Axios.post("/job/totalsssu", {
id: r.data.insert_jobs.returning[0].id id: r.data.insert_jobs.returning[0].id
}); });
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(r.data.insert_jobs.returning[0].id, notification);
}
notification["success"]({ notification["success"]({
message: t("jobs.successes.created"), message: t("jobs.successes.created"),
onClick: () => { onClick: () => {
@@ -281,6 +281,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
if (CriticalPartsScanning.treatment === "on") { if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id, notification); CriticalPartsScan(updateResult.data.update_jobs.returning[0].id, notification);
} }
if (updateResult.errors) { if (updateResult.errors) {
//error while inserting //error while inserting
notification["error"]({ notification["error"]({

View File

@@ -21,7 +21,7 @@ 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 ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
import { SiMicrosoftteams } from "react-icons/si"; 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));
@@ -424,7 +424,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
noIcon={true} noIcon={true}
linkText={ linkText={
<div className="share-to-teams-badge"> <div className="share-to-teams-badge">
<SiMicrosoftteams /> <PiMicrosoftTeamsLogo />
</div> </div>
} }
urlOverride={`${window.location.origin}/manage/jobs/${card.id}`} urlOverride={`${window.location.origin}/manage/jobs/${card.id}`}

View File

@@ -1,7 +1,7 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Button } from "antd"; import { Button } from "antd";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { SiMicrosoftteams } from "react-icons/si"; import { PiMicrosoftTeamsLogo } from "react-icons/pi";
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";
@@ -90,7 +90,7 @@ const ShareToTeamsComponent = ({
color: "#FFFFFF", color: "#FFFFFF",
...buttonStyle ...buttonStyle
}} }}
icon={<SiMicrosoftteams style={{ color: "#FFFFFF", ...buttonIconStyle }} />} icon={<PiMicrosoftTeamsLogo style={{ color: "#FFFFFF", ...buttonIconStyle }} />}
onClick={handleShare} onClick={handleShare}
title={linkText === null ? t("general.actions.sharetoteams") : linkText} title={linkText === null ? t("general.actions.sharetoteams") : linkText}
/> />

View File

@@ -1,16 +1,27 @@
import {DeleteFilled} from "@ant-design/icons"; import { DeleteFilled } from "@ant-design/icons";
import {Button, Col, Form, Input, Row, Select, Space, Switch} from "antd"; import { Button, Col, Form, Input, Row, Select, Space, Switch } from "antd";
import React, {useMemo} from "react"; import { useMemo } from "react";
import {useTranslation} from "react-i18next"; import { useTranslation } from "react-i18next";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import i18n from "i18next";
const predefinedPartTypes = [ const predefinedPartTypes = ["PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG"];
"PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG"
];
const predefinedModLbrTypes = [ const predefinedModLbrTypes = [
"LAA", "LAB", "LAD", "LAE", "LAF", "LAG", "LAM", "LAR", "LAS", "LAU", "LAA",
"LA1", "LA2", "LA3", "LA4" "LAB",
"LAD",
"LAE",
"LAF",
"LAG",
"LAM",
"LAR",
"LAS",
"LAU",
"LA1",
"LA2",
"LA3",
"LA4"
]; ];
const getFieldType = (field) => { const getFieldType = (field) => {
@@ -20,30 +31,46 @@ const getFieldType = (field) => {
return null; return null;
}; };
export default function ShopInfoPartsScan({form}) { const fieldSelectOptions = [
const {t} = useTranslation(); { label: i18n.t("joblines.fields.line_desc"), value: "line_desc" },
{ label: i18n.t("joblines.fields.part_type"), value: "part_type" },
{ label: i18n.t("joblines.fields.act_price"), value: "act_price" },
{ label: i18n.t("joblines.fields.part_qty"), value: "part_qty" },
{ label: i18n.t("joblines.fields.mod_lbr_ty"), value: "mod_lbr_ty" },
{
label: `${i18n.t("joblines.fields.oem_partno")} / ${i18n.t("joblines.fields.alt_partno")}`,
value: "part_number"
},
{ label: i18n.t("joblines.fields.op_code_desc"), value: "op_code_desc" }
];
export default function ShopInfoPartsScan({ form }) {
const { t } = useTranslation();
const watchedFields = Form.useWatch("md_parts_scan", form); const watchedFields = Form.useWatch("md_parts_scan", form);
const operationOptions = useMemo(() => ({ const operationOptions = useMemo(
string: [ () => ({
{label: t("bodyshop.operations.contains"), value: "contains"}, string: [
{label: t("bodyshop.operations.equals"), value: "equals"}, { label: t("bodyshop.operations.contains"), value: "contains" },
{label: t("bodyshop.operations.starts_with"), value: "startsWith"}, { label: t("bodyshop.operations.equals"), value: "equals" },
{label: t("bodyshop.operations.ends_with"), value: "endsWith"}, { label: t("bodyshop.operations.starts_with"), value: "startsWith" },
], { label: t("bodyshop.operations.ends_with"), value: "endsWith" }
number: [ ],
{label: t("bodyshop.operations.equals"), value: "="}, number: [
{label: t("bodyshop.operations.greater_than"), value: ">"}, { label: t("bodyshop.operations.equals"), value: "=" },
{label: t("bodyshop.operations.less_than"), value: "<"}, { label: t("bodyshop.operations.greater_than"), value: ">" },
], { label: t("bodyshop.operations.less_than"), value: "<" }
}), [t]); ]
}),
[t]
);
return ( return (
<div> <div>
<LayoutFormRow header={t("bodyshop.labels.md_parts_scan")}> <LayoutFormRow header={t("bodyshop.labels.md_parts_scan")}>
<Form.List name={["md_parts_scan"]}> <Form.List name={["md_parts_scan"]}>
{(fields, {add, remove, move}) => ( {(fields, { add, remove, move }) => (
<div> <div>
{fields.map((field, index) => { {fields.map((field, index) => {
const selectedField = watchedFields?.[index]?.field || "line_desc"; const selectedField = watchedFields?.[index]?.field || "line_desc";
@@ -61,28 +88,17 @@ export default function ShopInfoPartsScan({form}) {
{ {
required: true, required: true,
message: t("general.validation.required", { message: t("general.validation.required", {
label: t("bodyshop.fields.md_parts_scan.field"), label: t("bodyshop.fields.md_parts_scan.field")
}), })
}, }
]} ]}
> >
<Select <Select
options={[ options={fieldSelectOptions}
{label: t("joblines.fields.line_desc"), value: "line_desc"},
{label: t("joblines.fields.part_type"), value: "part_type"},
{label: t("joblines.fields.act_price"), value: "act_price"},
{label: t("joblines.fields.part_qty"), value: "part_qty"},
{label: t("joblines.fields.mod_lbr_ty"), value: "mod_lbr_ty"},
{label: t("joblines.fields.mod_lb_hrs"), value: "mod_lb_hrs"},
{
label: `${t("joblines.fields.oem_partno")} / ${t("joblines.fields.alt_partno")}`,
value: "part_number"
},
]}
onChange={() => { onChange={() => {
form.setFields([ form.setFields([
{name: ["md_parts_scan", index, "operation"], value: "contains"}, { name: ["md_parts_scan", index, "operation"], value: "contains" },
{name: ["md_parts_scan", index, "value"], value: undefined}, { name: ["md_parts_scan", index, "value"], value: undefined }
]); ]);
}} }}
/> />
@@ -99,12 +115,12 @@ export default function ShopInfoPartsScan({form}) {
{ {
required: true, required: true,
message: t("general.validation.required", { message: t("general.validation.required", {
label: t("bodyshop.fields.md_parts_scan.operation"), label: t("bodyshop.fields.md_parts_scan.operation")
}), })
}, }
]} ]}
> >
<Select options={operationOptions[fieldType]}/> <Select options={operationOptions[fieldType]} />
</Form.Item> </Form.Item>
</Col> </Col>
)} )}
@@ -119,9 +135,9 @@ export default function ShopInfoPartsScan({form}) {
{ {
required: true, required: true,
message: t("general.validation.required", { message: t("general.validation.required", {
label: t("bodyshop.fields.md_parts_scan.value"), label: t("bodyshop.fields.md_parts_scan.value")
}), })
}, }
]} ]}
> >
{fieldType === "predefined" ? ( {fieldType === "predefined" ? (
@@ -129,17 +145,17 @@ export default function ShopInfoPartsScan({form}) {
options={ options={
selectedField === "part_type" selectedField === "part_type"
? predefinedPartTypes.map((type) => ({ ? predefinedPartTypes.map((type) => ({
label: type, label: type,
value: type value: type
})) }))
: predefinedModLbrTypes.map((type) => ({ : predefinedModLbrTypes.map((type) => ({
label: type, label: type,
value: type value: type
})) }))
} }
/> />
) : ( ) : (
<Input/> <Input />
)} )}
</Form.Item> </Form.Item>
</Col> </Col>
@@ -152,19 +168,70 @@ export default function ShopInfoPartsScan({form}) {
label={t("bodyshop.fields.md_parts_scan.caseInsensitive")} label={t("bodyshop.fields.md_parts_scan.caseInsensitive")}
name={[field.name, "caseInsensitive"]} name={[field.name, "caseInsensitive"]}
valuePropName="checked" valuePropName="checked"
labelCol={{span: 14}} initialValue={true}
wrapperCol={{span: 10}} labelCol={{ span: 14 }}
wrapperCol={{ span: 10 }}
> >
<Switch defaultChecked={true}/> <Switch />
</Form.Item> </Form.Item>
</Col> </Col>
)} )}
{/* Mark Line as Critical */}
<Col span={4}>
<Form.Item
label={t("bodyshop.fields.md_parts_scan.mark_critical")}
name={[field.name, "mark_critical"]}
valuePropName="checked"
initialValue={true}
labelCol={{ span: 14 }}
wrapperCol={{ span: 10 }}
>
<Switch />
</Form.Item>
</Col>
{/* Update Field */}
<Col span={4}>
<Form.Item
label={t("bodyshop.fields.md_parts_scan.update_field")}
name={[field.name, "update_field"]}
>
<Select
options={fieldSelectOptions}
allowClear
onClear={() =>
form.setFields([{ name: ["md_parts_scan", index, "update_field"], value: null }])
}
/>
</Form.Item>
</Col>
{/* Update Field */}
<Col span={4}>
<Form.Item
label={t("bodyshop.fields.md_parts_scan.update_value")}
name={[field.name, "update_value"]}
dependencies={[["md_parts_scan", index, "update_field"]]}
tooltip={t("bodyshop.tooltips.md_parts_scan.update_value_tooltip")}
rules={[
{
required: form.getFieldValue(["md_parts_scan", index, "update_field"]),
message: t("general.validation.required", {
label: t("bodyshop.fields.md_parts_scan.update_value")
})
}
]}
>
<Input />
</Form.Item>
</Col>
{/* Actions */} {/* Actions */}
<Col span={2}> <Col span={2}>
<Space> <Space>
<DeleteFilled onClick={() => remove(field.name)}/> <DeleteFilled onClick={() => remove(field.name)} />
<FormListMoveArrows move={move} index={index} total={fields.length}/> <FormListMoveArrows move={move} index={index} total={fields.length} />
</Space> </Space>
</Col> </Col>
</Row> </Row>
@@ -175,8 +242,8 @@ export default function ShopInfoPartsScan({form}) {
<Form.Item> <Form.Item>
<Button <Button
type="dashed" type="dashed"
onClick={() => add({field: "line_desc", operation: "contains"})} onClick={() => add({ field: "line_desc", operation: "contains" })}
style={{width: "100%"}} style={{ width: "100%" }}
> >
{t("bodyshop.actions.addpartsrule")} {t("bodyshop.actions.addpartsrule")}
</Button> </Button>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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) => {

View File

@@ -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 };
} }
/** /**

View File

@@ -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"
} }
} }
}, },

3052
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -2241,6 +2241,7 @@ exports.QUERY_PARTS_SCAN = `query QUERY_PARTS_SCAN ($id: uuid!) {
mod_lb_hrs mod_lb_hrs
oem_partno oem_partno
alt_partno alt_partno
op_code_desc
} }
} }
}`; }`;
@@ -2252,7 +2253,7 @@ exports.UPDATE_PARTS_CRITICAL = `mutation UPDATE_PARTS_CRITICAL ($IdsToMarkCriti
notcritical: update_joblines(where: {id: {_nin: $IdsToMarkCritical}, jobid: {_eq: $jobid}}, _set: {critical: false}) { notcritical: update_joblines(where: {id: {_nin: $IdsToMarkCritical}, jobid: {_eq: $jobid}}, _set: {critical: false}) {
affected_rows affected_rows
} }
}` }`;
exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) { exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) {
associations(where: {active: {_eq: true}, useremail: {_eq: $user}}) { associations(where: {active: {_eq: true}, useremail: {_eq: $user}}) {
@@ -2618,7 +2619,6 @@ exports.CREATE_CONVERSATION = `mutation CREATE_CONVERSATION($conversation: [conv
} }
`; `;
exports.STATUS_UPDATE = `query STATUS_UPDATE($period: timestamptz!, $today: timestamptz!) { exports.STATUS_UPDATE = `query STATUS_UPDATE($period: timestamptz!, $today: timestamptz!) {
bodyshops(where: { created_at: { _gte: $period } }) { bodyshops(where: { created_at: { _gte: $period } }) {
shopname shopname
@@ -2689,4 +2689,19 @@ exports.STATUS_UPDATE = `query STATUS_UPDATE($period: timestamptz!, $today: time
} }
} }
} }
` `;
exports.INSERT_AUDIT_TRAIL = `
mutation INSERT_AUDIT_TRAIL($auditObj: audit_trail_insert_input!) {
insert_audit_trail_one(object: $auditObj) {
id
jobid
billid
bodyshopid
created
operation
type
useremail
}
}
`;

View File

@@ -1,16 +1,25 @@
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const predefinedPartTypes = [ const predefinedPartTypes = ["PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG"];
"PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG",
];
const predefinedModLbrTypes = [ const predefinedModLbrTypes = [
"LAA", "LAB", "LAD", "LAE", "LAF", "LAG", "LAM", "LAR", "LAS", "LAU", "LAA",
"LA1", "LA2", "LA3", "LA4", "LAB",
"LAD",
"LAE",
"LAF",
"LAG",
"LAM",
"LAR",
"LAS",
"LAU",
"LA1",
"LA2",
"LA3",
"LA4"
]; ];
exports.partsScan = async function (req, res) { exports.partsScan = async function (req, res) {
console.log('hello')
const { jobid } = req.body; const { jobid } = req.body;
const BearerToken = req.BearerToken; const BearerToken = req.BearerToken;
const client = req.userGraphQLClient; const client = req.userGraphQLClient;
@@ -18,10 +27,9 @@ exports.partsScan = async function (req, res) {
logger.log("job-parts-scan", "DEBUG", req.user?.email, jobid, null); logger.log("job-parts-scan", "DEBUG", req.user?.email, jobid, null);
try { try {
const data = await client.setHeaders({ Authorization: BearerToken }).request( const data = await client
queries.QUERY_PARTS_SCAN, .setHeaders({ Authorization: BearerToken })
{ id: jobid } .request(queries.QUERY_PARTS_SCAN, { id: jobid });
);
const rules = data.jobs_by_pk.bodyshop.md_parts_scan || []; const rules = data.jobs_by_pk.bodyshop.md_parts_scan || [];
if (!Array.isArray(rules)) { if (!Array.isArray(rules)) {
@@ -36,21 +44,22 @@ exports.partsScan = async function (req, res) {
? new RegExp(rule.value, "i") ? new RegExp(rule.value, "i")
: typeof rule.value === "string" : typeof rule.value === "string"
? new RegExp(rule.value) ? new RegExp(rule.value)
: null, : null
})); }));
const criticalIds = new Set(); const criticalIds = new Set();
for (const jobline of data.jobs_by_pk.joblines) { for (const jobline of data.jobs_by_pk.joblines) {
for (const { field, regex, operation, value } of compiledRules) { for (const { field, regex, operation, value, mark_critical, update_field, update_value } of compiledRules) {
if (criticalIds.has(jobline.id)) break; // Skip further evaluation if already critical // IO-3077 - Remove skip as this is extended to include line updates.
// if (criticalIds.has(jobline.id)) break; // Skip further evaluation if already critical
let jobValue = jobline[field]; let jobValue = jobline[field];
let match = false; let match = false;
if (field === "part_number") { if (field === "part_number") {
match = regex match = regex
? regex.test(jobline.oem_partno || '') || regex.test(jobline.alt_partno || '') ? regex.test(jobline.oem_partno || "") || regex.test(jobline.alt_partno || "")
: jobline.oem_partno === value || jobline.alt_partno === value; : jobline.oem_partno === value || jobline.alt_partno === value;
} else if (field === "part_type") { } else if (field === "part_type") {
match = predefinedPartTypes.includes(value) && value === jobValue; match = predefinedPartTypes.includes(value) && value === jobValue;
@@ -68,22 +77,44 @@ exports.partsScan = async function (req, res) {
} }
if (match) { if (match) {
criticalIds.add(jobline.id); if (mark_critical) {
break; // No need to evaluate further rules for this jobline //Could technically lead to duplicates, but they'd only be n positives, ultimately leading a positive.
criticalIds.add(jobline.id);
}
if (update_field && update_value) {
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_JOB_LINE, {
lineId: jobline.id,
line: { [update_field]: update_value, manual_line: true }
});
const auditResult = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.INSERT_AUDIT_TRAIL, {
auditObj: {
bodyshopid: data.jobs_by_pk.bodyshop.id,
jobid,
operation: `Jobline (#${jobline.line_no} ${jobline.line_desc}/${jobline.id}) ${update_field} updated from ${jobline[update_field]} to ${update_value}. Lined marked as manual line.`,
type: "partscanupdate",
useremail: req.user.email
}
});
}
//break; // No need to evaluate further rules for this jobline
} }
} }
} }
const result = await client.setHeaders({ Authorization: BearerToken }).request( const result = await client
queries.UPDATE_PARTS_CRITICAL, .setHeaders({ Authorization: BearerToken })
{ IdsToMarkCritical: Array.from(criticalIds), jobid } .request(queries.UPDATE_PARTS_CRITICAL, { IdsToMarkCritical: Array.from(criticalIds), jobid });
);
res.status(200).json(result); res.status(200).json(result);
} catch (error) { } catch (error) {
logger.log("job-parts-scan-error", "ERROR", req.user?.email, jobid, { logger.log("job-parts-scan-error", "ERROR", req.user?.email, jobid, {
jobid, jobid,
error: error.message, error: error.message,
stack: error.stack
}); });
res.status(400).json(JSON.stringify({ message: error?.message })); res.status(400).json(JSON.stringify({ message: error?.message }));
} }

View File

@@ -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 }));
}; };

View File

@@ -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;