Merged in release/2025-01-31 (pull request #2100)

Release/2025 01 31 into test-AIO - IO-3096 IO-2825 IO-3123
This commit is contained in:
Dave Richer
2025-02-04 18:04:45 +00:00
22 changed files with 7542 additions and 5622 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 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

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

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

@@ -134,10 +134,10 @@ export function JobsDetailHeaderActions({
const notification = useNotification(); const notification = useNotification();
const { const {
treatments: { ImEXPay, Share_To_Teams } treatments: { ImEXPay }
} = useSplitTreatments({ } = useSplitTreatments({
attributes: {}, attributes: {},
names: ["ImEXPay", "Share_To_Teams"], names: ["ImEXPay"],
splitKey: bodyshop && bodyshop.imexshopid splitKey: bodyshop && bodyshop.imexshopid
}); });
@@ -202,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);
@@ -839,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)
} }
); );
@@ -972,7 +975,7 @@ export function JobsDetailHeaderActions({
} }
); );
if (Share_To_Teams?.treatment === "on") { if (bodyshop?.md_functionality_toggles?.teams) {
menuItems.push({ menuItems.push({
key: "sharetoteams", key: "sharetoteams",
id: "job-actions-sharetoteams", id: "job-actions-sharetoteams",

View File

@@ -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,
@@ -39,7 +40,7 @@ export function JobsDetailHeaderActionsToggleProduction({
const [form] = Form.useForm(); const [form] = Form.useForm();
const notification = useNotification(); const notification = useNotification();
const [getJobDetails] = useLazyQuery(GET_JOB_BY_PK_QUICK_INTAKE, { const [getJobDetails, { loading: jobDetailsLoading }] = useLazyQuery(GET_JOB_BY_PK_QUICK_INTAKE, {
variables: { id: job.id }, variables: { id: job.id },
onCompleted: (data) => { onCompleted: (data) => {
if (data?.jobs_by_pk) { if (data?.jobs_by_pk) {
@@ -109,65 +110,69 @@ export function JobsDetailHeaderActionsToggleProduction({
const popMenu = ( const popMenu = (
<div onClick={(e) => e.stopPropagation()}> <div onClick={(e) => e.stopPropagation()}>
<Form layout="vertical" form={form} onFinish={handleConvert}> {jobDetailsLoading ? (
{scenario === "pre" && ( <LoadingSpinner />
<> ) : (
<Form.Item <Form layout="vertical" form={form} onFinish={handleConvert}>
name={["actual_in"]} {scenario === "pre" && (
label={t("jobs.fields.actual_in")} <>
rules={[ <Form.Item
{ name={["actual_in"]}
required: true label={t("jobs.fields.actual_in")}
//message: t("general.validation.required"), rules={[
} {
]} required: true
> //message: t("general.validation.required"),
<FormDateTimePickerComponent disabled={jobRO} /> }
</Form.Item> ]}
<Form.Item >
name={["scheduled_completion"]} <FormDateTimePickerComponent disabled={jobRO} />
label={t("jobs.fields.scheduled_completion")} </Form.Item>
rules={[ <Form.Item
{ name={["scheduled_completion"]}
required: true label={t("jobs.fields.scheduled_completion")}
//message: t("general.validation.required"), rules={[
} {
]} required: true
> //message: t("general.validation.required"),
<FormDateTimePickerComponent disabled={jobRO} /> }
</Form.Item> ]}
<Form.Item name={["scheduled_delivery"]} label={t("jobs.fields.scheduled_delivery")}> >
<FormDateTimePickerComponent disabled={jobRO} /> <FormDateTimePickerComponent disabled={jobRO} />
</Form.Item> </Form.Item>
</> <Form.Item name={["scheduled_delivery"]} label={t("jobs.fields.scheduled_delivery")}>
)} <FormDateTimePickerComponent disabled={jobRO} />
{scenario === "prod" && ( </Form.Item>
<> </>
<Form.Item )}
name={["actual_completion"]} {scenario === "prod" && (
label={t("jobs.fields.actual_completion")} <>
rules={[ <Form.Item
{ name={["actual_completion"]}
required: true label={t("jobs.fields.actual_completion")}
//message: t("general.validation.required"), rules={[
} {
]} required: true
> //message: t("general.validation.required"),
<FormDateTimePickerComponent disabled={jobRO} /> }
</Form.Item> ]}
>
<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>
); );

View File

@@ -67,7 +67,6 @@ 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;
// label: <ShareToTeamsButton noIcon={true} urlOverride={`${window.location.origin}${window.location.pathname}`} />
const recordActions = (record, showView = false) => ( const recordActions = (record, showView = false) => (
<Space direction="horizontal" wrap> <Space direction="horizontal" wrap>
<ShareToTeamsButton <ShareToTeamsButton

View File

@@ -1,5 +1,4 @@
import { Button, Input, Space, Spin } from "antd"; import { Button, Input, Space, Spin } from "antd";
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";
@@ -24,19 +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 [unassignedFilter, setUnassignedFilter] = useState(false);
const toggleAlertFilter = () => { const toggleAlertFilter = () => {
const newAlertFilter = !alertFilter; setFilter({ ...filter, alert: !filter.alert });
setAlertFilter(newAlertFilter);
setFilter({ ...filter, alert: newAlertFilter });
}; };
const toggleUnassignedFilter = () => { const toggleUnassignedFilter = () => {
const newUnassignedFilter = !unassignedFilter; setFilter({ ...filter, unassigned: !filter.unassigned });
setUnassignedFilter(newUnassignedFilter);
setFilter({ ...filter, unassigned: newUnassignedFilter });
}; };
return ( return (
@@ -58,16 +51,16 @@ 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 <Button
type={unassignedFilter ? "primary" : "default"} type={filter?.unassigned ? "primary" : "default"}
onClick={toggleUnassignedFilter} onClick={toggleUnassignedFilter}
icon={unassignedFilter ? <UserDeleteOutlined /> : <UsergroupDeleteOutlined />} icon={filter?.unassigned ? <UserDeleteOutlined /> : <UsergroupDeleteOutlined />}
> >
{t("production.labels.unassigned")} {t("production.labels.unassigned")}
</Button> </Button>

View File

@@ -3,7 +3,6 @@ import { Button } from "antd";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { SiMicrosoftteams } from "react-icons/si"; import { SiMicrosoftteams } from "react-icons/si";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
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.js"; import { selectBodyshop } from "../../redux/user/user.selectors.js";
@@ -43,14 +42,6 @@ const ShareToTeamsComponent = ({
const location = useLocation(); const location = useLocation();
const { t } = useTranslation(); const { t } = useTranslation();
const {
treatments: { Share_To_Teams }
} = useSplitTreatments({
attributes: {},
names: ["Share_To_Teams"],
splitKey: bodyshop && bodyshop.imexshopid
});
const currentUrl = const currentUrl =
urlOverride || urlOverride ||
encodeURIComponent(`${window.location.origin}${location.pathname}${location.search}${location.hash}`); encodeURIComponent(`${window.location.origin}${location.pathname}${location.search}${location.hash}`);
@@ -78,7 +69,8 @@ const ShareToTeamsComponent = ({
window.open(teamsShareUrl, "_blank", windowFeatures); window.open(teamsShareUrl, "_blank", windowFeatures);
}; };
if (Share_To_Teams?.treatment !== "on") { // Feature is disabled
if (!bodyshop?.md_functionality_toggles?.teams) {
return null; return null;
} }

View File

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

View File

@@ -269,7 +269,7 @@ function TaskListComponent({
<Space direction="horizontal"> <Space direction="horizontal">
<ShareToTeamsButton <ShareToTeamsButton
linkText="" linkText=""
urlOverride={`https://localhost:3000/manage/tasks/alltasks?taskid=${record.id}`} urlOverride={`${window.location.origin}/manage/tasks/alltasks?taskid=${record.id}`}
/> />
<Button <Button
title={t("tasks.buttons.edit")} title={t("tasks.buttons.edit")}

View File

@@ -235,7 +235,12 @@ export function* signInSuccessSaga({ payload }) {
try { try {
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]); window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
window.$crisp.push(["set", "session:segments", [["user"]]]); 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: [],

View File

@@ -712,7 +712,8 @@
"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",

View File

@@ -712,7 +712,8 @@
"ssbuckets": "", "ssbuckets": "",
"systemsettings": "", "systemsettings": "",
"task-presets": "", "task-presets": "",
"workingdays": "" "workingdays": "",
"shop_enabled_features": ""
}, },
"operations": { "operations": {
"contains": "", "contains": "",

View File

@@ -712,7 +712,8 @@
"ssbuckets": "", "ssbuckets": "",
"systemsettings": "", "systemsettings": "",
"task-presets": "", "task-presets": "",
"workingdays": "" "workingdays": "",
"shop_enabled_features": ""
}, },
"operations": { "operations": {
"contains": "", "contains": "",

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

View File

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

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

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