Compare commits
22 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
831802f5af | ||
|
|
6005eaee6a | ||
|
|
c069600cfd | ||
|
|
186cbf2c97 | ||
|
|
392988ae11 | ||
|
|
2e33b79eb9 | ||
|
|
fa99ef7b37 | ||
|
|
c4aff1b516 | ||
|
|
61276bb2d1 | ||
|
|
8b89e2eb9d | ||
|
|
9ab41308e7 | ||
|
|
f76052ec9b | ||
|
|
b8841e3ded | ||
|
|
a49b3f6496 | ||
|
|
3e17ec3cf8 | ||
|
|
76c0c7c41e | ||
|
|
025b986f60 | ||
|
|
6e6addd62f | ||
|
|
266c3acf34 | ||
|
|
ca18291425 | ||
|
|
a9814c1eb1 | ||
|
|
12c87ed689 |
110
client/package-lock.json
generated
110
client/package-lock.json
generated
@@ -21,13 +21,13 @@
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.8.1",
|
||||
"@sentry/cli": "^2.45.0",
|
||||
"@sentry/react": "^9.17.0",
|
||||
"@sentry/react": "^9.18.0",
|
||||
"@sentry/vite-plugin": "^3.4.0",
|
||||
"@splitsoftware/splitio-react": "^2.1.1",
|
||||
"@tanem/react-nprogress": "^5.0.53",
|
||||
"antd": "^5.25.0",
|
||||
"antd": "^5.25.1",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^4.2.0",
|
||||
"apollo-link-sentry": "^4.3.0",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.8.4",
|
||||
"classnames": "^2.5.1",
|
||||
@@ -78,7 +78,7 @@
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.1",
|
||||
"sass": "^1.86.3",
|
||||
"sass": "^1.88.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"styled-components": "^6.1.18",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
@@ -90,7 +90,7 @@
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@dotenvx/dotenvx": "^1.43.0",
|
||||
"@dotenvx/dotenvx": "^1.44.0",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@eslint/js": "^9.26.0",
|
||||
@@ -2587,9 +2587,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@dotenvx/dotenvx": {
|
||||
"version": "1.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.43.0.tgz",
|
||||
"integrity": "sha512-Z8XjM75aWZ/ekUzBjlr/OqQsLWtJY4nVtruxopAt+FlYHfY0/gKl85nD16aEqbTkU53kJcm5psID0L2/sQMmuw==",
|
||||
"version": "1.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.44.0.tgz",
|
||||
"integrity": "sha512-18Aa+7KP/L2Kj9lxmT4EJZnsCq/xGIHgzU26rdzsKMhjpeT3YY+qin/dNAnIaVHPZnee7kXpZL55M9htd30r7Q==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
@@ -4458,50 +4458,50 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sentry-internal/browser-utils": {
|
||||
"version": "9.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.17.0.tgz",
|
||||
"integrity": "sha512-37n6NXtkUfdK7YiP3L5DJvhA/iusOmnjHQdX1e2VwI6a29xHCl/vRqLR3XNr5K4m+49al+3fWo2ltcKsfV+0xw==",
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.18.0.tgz",
|
||||
"integrity": "sha512-TwSlmgYpHhe55JpOcVApkM0XcXZh1/cYuEPKPFgeaaPD8BrQrLJJvwKxnonSWXOhdnkJxi4GgK7j7mw57PS4aA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "9.17.0"
|
||||
"@sentry/core": "9.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/feedback": {
|
||||
"version": "9.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.17.0.tgz",
|
||||
"integrity": "sha512-C2jBlGgYVGm8eXK38wlYQyd6NsHKaQlENg5fx8TDFMKWMNmLf6BmnPZ+y73OsFwcUtBz04CwZteybYB2GgYrvQ==",
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.18.0.tgz",
|
||||
"integrity": "sha512-QlrB8oQK+5bfhbgK6yHF6rLwLNJ9XuGblTc51yVkm4d4jn4W/HDyaNqMfQF+JXdTiFatl8oz2xdKR8kGK8kXyg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "9.17.0"
|
||||
"@sentry/core": "9.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay": {
|
||||
"version": "9.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.17.0.tgz",
|
||||
"integrity": "sha512-oH4NolXkEpe73eRP9r3K6WpERYItZisYQudsNrtkUBQL5M/uENiE7YTOvL5osD8AWmU0hCKY3Oua+qDi2lB+8g==",
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.18.0.tgz",
|
||||
"integrity": "sha512-2A32FFwrlZtdpBruvpcLEfucu6BpyqOk3F4Bo5smM/5q7u0pa7q5d9FSY5l3nwKEAFAoLGv3hcCb+8wxMm50xA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "9.17.0",
|
||||
"@sentry/core": "9.17.0"
|
||||
"@sentry-internal/browser-utils": "9.18.0",
|
||||
"@sentry/core": "9.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay-canvas": {
|
||||
"version": "9.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.17.0.tgz",
|
||||
"integrity": "sha512-w9AxBJIa+MbxDngvwnqouoJ/ezb7wNjxzFXtmaVtGp7hbC4yme/TOTNtFYg2J/ceQf3GMc8AfW5tsP6zU0R7gg==",
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.18.0.tgz",
|
||||
"integrity": "sha512-3DEyQLmHcYgcwJ8n8eMhI6bhhawPuMc2xTT+Az8gXMqCO/X9ZACpipAmhXFjYP9Ptl+w0Vh3nllJw+gXc/DOsg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/replay": "9.17.0",
|
||||
"@sentry/core": "9.17.0"
|
||||
"@sentry-internal/replay": "9.18.0",
|
||||
"@sentry/core": "9.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -4517,16 +4517,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/browser": {
|
||||
"version": "9.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.17.0.tgz",
|
||||
"integrity": "sha512-3e/Q5bv06Q+XYV2cKmUgfMfnJtBY8MZKufpcwQ2ab2eMrastqau9KjYeWXapskDm179oPLfzLcDCSlDSTcvqpQ==",
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.18.0.tgz",
|
||||
"integrity": "sha512-0SWfp4J2+mH4lZOcHfyIwt9VoGD7yCGQE1cm0BPcLwKnrVQeXHtUXNYNy8HTHSjTGyoFDhEAYelE/tdA3OLcWQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "9.17.0",
|
||||
"@sentry-internal/feedback": "9.17.0",
|
||||
"@sentry-internal/replay": "9.17.0",
|
||||
"@sentry-internal/replay-canvas": "9.17.0",
|
||||
"@sentry/core": "9.17.0"
|
||||
"@sentry-internal/browser-utils": "9.18.0",
|
||||
"@sentry-internal/feedback": "9.18.0",
|
||||
"@sentry-internal/replay": "9.18.0",
|
||||
"@sentry-internal/replay-canvas": "9.18.0",
|
||||
"@sentry/core": "9.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -4899,22 +4899,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "9.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.17.0.tgz",
|
||||
"integrity": "sha512-9f1A93/kY9lLH06L1thPx94IhyLjEP3aRxYAtjtBfzId8UtubSpwP92sbxgslodD73R4tURwWJj7nYZ9HLYBUg==",
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.18.0.tgz",
|
||||
"integrity": "sha512-kRVH8BqMiaU2FTHYa68zNlAloS43jl4XtIEHkLKVH/7gUtwRmM4Gqj8P7RTrZdO1Lo7ksYnGj+AG05Z09CRbOw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/react": {
|
||||
"version": "9.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.17.0.tgz",
|
||||
"integrity": "sha512-hJOVUheFoUKr5e4vHxyKiu72FRgqmMTFIUG9myim8PH8mJYDqab7Z7cOt4dsBR86soKanaRB5PJq5jGFuipLfg==",
|
||||
"version": "9.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.18.0.tgz",
|
||||
"integrity": "sha512-1cCLYZrZ2gu6H8eE83DC47kLf+pzD1Rim3dDoOEvwt1F5cD3K/DBeIhoHZaXqBeQxuVyHXOOLXSAC/CIuas5Aw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/browser": "9.17.0",
|
||||
"@sentry/core": "9.17.0",
|
||||
"@sentry/browser": "9.18.0",
|
||||
"@sentry/core": "9.18.0",
|
||||
"hoist-non-react-statics": "^3.3.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -6088,9 +6088,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/antd": {
|
||||
"version": "5.25.0",
|
||||
"resolved": "https://registry.npmjs.org/antd/-/antd-5.25.0.tgz",
|
||||
"integrity": "sha512-p9d8Kuj/bipjNdg9NrTu1VmTrhcwIhURu2NfK6qaBMbb+LRyFdAUoseT+7J4a+5z3jNVjxH5zaYv/45Zf8Coyg==",
|
||||
"version": "5.25.1",
|
||||
"resolved": "https://registry.npmjs.org/antd/-/antd-5.25.1.tgz",
|
||||
"integrity": "sha512-4KC7KuPCjr0z3Vuw9DsF+ceqJaPLbuUI3lOX1sY8ix25ceamp+P8yxOmk3Y2JHCD2ZAhq+5IQ/DTJRN2adWYKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^7.2.0",
|
||||
@@ -6128,7 +6128,7 @@
|
||||
"rc-rate": "~2.13.1",
|
||||
"rc-resize-observer": "^1.4.3",
|
||||
"rc-segmented": "~2.7.0",
|
||||
"rc-select": "~14.16.6",
|
||||
"rc-select": "~14.16.7",
|
||||
"rc-slider": "~11.1.8",
|
||||
"rc-steps": "~6.0.1",
|
||||
"rc-switch": "~4.1.0",
|
||||
@@ -6232,9 +6232,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/apollo-link-sentry": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-sentry/-/apollo-link-sentry-4.2.0.tgz",
|
||||
"integrity": "sha512-w8EUM4aEw1/VxIB3KOP11T8qz44oWRcbXRd2vJq/qHnfRMKS5HkMerSIYwKN2e8k9H8ubfkwBvStH51CVf4wwg==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/apollo-link-sentry/-/apollo-link-sentry-4.3.0.tgz",
|
||||
"integrity": "sha512-C3WK4iwIzW5vC5BoY3VPdKjm16P6ca/LGKFnxg6PvUuboxPlqs7LHQCYvEsdAxBkoY+8kRXd8Q3+3oU+HHUceA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"deepmerge": "^4.2.2",
|
||||
@@ -14024,9 +14024,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rc-select": {
|
||||
"version": "14.16.6",
|
||||
"resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.6.tgz",
|
||||
"integrity": "sha512-YPMtRPqfZWOm2XGTbx5/YVr1HT0vn//8QS77At0Gjb3Lv+Lbut0IORJPKLWu1hQ3u4GsA0SrDzs7nI8JG7Zmyg==",
|
||||
"version": "14.16.7",
|
||||
"resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.7.tgz",
|
||||
"integrity": "sha512-lT9kO5gFHQdJzu9a0btcOtNaJHkhenSl8H5mcpgXN9VIMXP59rnkpbdHmPrteixWs1D5zFOTyoTYX3b7joADIQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.10.1",
|
||||
@@ -15375,9 +15375,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.87.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.87.0.tgz",
|
||||
"integrity": "sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==",
|
||||
"version": "1.88.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.88.0.tgz",
|
||||
"integrity": "sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.8.1",
|
||||
"@sentry/cli": "^2.45.0",
|
||||
"@sentry/react": "^9.17.0",
|
||||
"@sentry/react": "^9.18.0",
|
||||
"@sentry/vite-plugin": "^3.4.0",
|
||||
"@splitsoftware/splitio-react": "^2.1.1",
|
||||
"@tanem/react-nprogress": "^5.0.53",
|
||||
"antd": "^5.25.0",
|
||||
"antd": "^5.25.1",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^4.2.0",
|
||||
"apollo-link-sentry": "^4.3.0",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.8.4",
|
||||
"classnames": "^2.5.1",
|
||||
@@ -77,7 +77,7 @@
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.1",
|
||||
"sass": "^1.86.3",
|
||||
"sass": "^1.88.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"styled-components": "^6.1.18",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
@@ -130,7 +130,7 @@
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@dotenvx/dotenvx": "^1.43.0",
|
||||
"@dotenvx/dotenvx": "^1.44.0",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@eslint/js": "^9.26.0",
|
||||
|
||||
@@ -14,8 +14,21 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
setPartsOrderContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "partsOrder"
|
||||
})
|
||||
),
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditReturn);
|
||||
@@ -69,7 +82,7 @@ export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, b
|
||||
<Modal
|
||||
open={open}
|
||||
onCancel={() => setOpen(false)}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
title={t("bills.actions.return")}
|
||||
onOk={() => form.submit()}
|
||||
>
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function BillDetailEditcontainer() {
|
||||
delete search.billid;
|
||||
history({ search: queryString.stringify(search) });
|
||||
}}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
open={search.billid}
|
||||
>
|
||||
<BillDetailEditComponent />
|
||||
|
||||
@@ -412,7 +412,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
)}
|
||||
</Space>
|
||||
}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form
|
||||
onFinish={handleFinish}
|
||||
|
||||
@@ -75,7 +75,7 @@ export function ContractsFindModalContainer({ caBcEtfTableModal, toggleModalVisi
|
||||
title={t("payments.labels.findermodal")}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
onOk={() => toggleModalVisible()}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
forceRender
|
||||
>
|
||||
<Form form={form} layout="vertical" autoComplete="no" onFinish={handleFinish}>
|
||||
|
||||
@@ -40,7 +40,7 @@ function CardPaymentModalContainer({ cardPaymentModal, toggleModalVisible, bodys
|
||||
</Button>
|
||||
]}
|
||||
width="80%"
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
>
|
||||
<CardPaymentModalComponent />
|
||||
</Modal>
|
||||
|
||||
@@ -63,7 +63,7 @@ export function ContractsFindModalContainer({
|
||||
title={t("contracts.labels.findermodal")}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
onOk={() => toggleModalVisible()}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
forceRender
|
||||
>
|
||||
<Form form={form} layout="vertical" autoComplete="no" onFinish={handleFinish}>
|
||||
|
||||
@@ -152,7 +152,7 @@ export function EmailOverlayContainer({ emailConfig, modalVisible, toggleEmailOv
|
||||
}, [modalVisible]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
return (
|
||||
<Modal
|
||||
destroyOnClose={true}
|
||||
destroyOnHidden
|
||||
open={modalVisible}
|
||||
maskClosable={false}
|
||||
width={"80%"}
|
||||
|
||||
@@ -51,6 +51,7 @@ import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import LockWrapper from "../lock-wrapper/lock-wrapper.component";
|
||||
import NotificationCenterContainer from "../notification-center/notification-center.container.jsx";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
||||
|
||||
// Redux mappings
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -98,6 +99,7 @@ function Header({
|
||||
const baseTitleRef = useRef(document.title || "");
|
||||
const lastSetTitleRef = useRef("");
|
||||
const userAssociationId = bodyshop?.associations?.[0]?.id;
|
||||
const isEmployee = useIsEmployee(bodyshop, currentUser);
|
||||
|
||||
const {
|
||||
data: unreadData,
|
||||
@@ -682,7 +684,7 @@ function Header({
|
||||
icon: unreadLoading ? (
|
||||
<Spin size="small" />
|
||||
) : (
|
||||
<Badge offset={[8, 0]} size="small" count={unreadCount}>
|
||||
<Badge offset={[8, 0]} size="small" count={isEmployee ? unreadCount : 0}>
|
||||
<BellFilled />
|
||||
</Badge>
|
||||
),
|
||||
|
||||
@@ -98,7 +98,7 @@ export function InventoryUpsertModalContainer({ currentUser, bodyshop, inventory
|
||||
onCancel={() => {
|
||||
toggleModalVisible();
|
||||
}}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form form={form} onFinish={handleFinish} layout="vertical">
|
||||
<InventoryUpsertModal form={form} />
|
||||
|
||||
@@ -66,7 +66,7 @@ export function ScheduleEventComponent({
|
||||
const [popOverVisible, setPopOverVisible] = useState(false);
|
||||
|
||||
const [getJobDetails] = useLazyQuery(GET_JOB_BY_PK_QUICK_INTAKE, {
|
||||
variables: { id: event.job.id },
|
||||
variables: { id: event.job?.id },
|
||||
onCompleted: (data) => {
|
||||
if (data?.jobs_by_pk) {
|
||||
const totalHours =
|
||||
@@ -83,6 +83,7 @@ export function ScheduleEventComponent({
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
@@ -409,8 +410,10 @@ export function ScheduleEventComponent({
|
||||
open={popOverVisible}
|
||||
onOpenChange={setPopOverVisible}
|
||||
onClick={(e) => {
|
||||
getJobDetails();
|
||||
e.stopPropagation();
|
||||
if (event.job?.id) {
|
||||
e.stopPropagation();
|
||||
getJobDetails();
|
||||
}
|
||||
}}
|
||||
getPopupContainer={(trigger) => trigger.parentNode}
|
||||
trigger="click"
|
||||
|
||||
@@ -49,7 +49,7 @@ export function JobCostingModalContainer({ jobCostingModal, toggleModalVisible }
|
||||
}}
|
||||
cancelButtonProps={{ style: { display: "none" } }}
|
||||
width="90%"
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
>
|
||||
{!costingData ? (
|
||||
<LoadingSpinner loading={true} />
|
||||
|
||||
@@ -32,7 +32,13 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "printCenter"
|
||||
})
|
||||
),
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
@@ -87,7 +93,7 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext, insertAuditTra
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer open={!!selected} destroyOnClose width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
||||
<Drawer open={!!selected} destroyOnHidden width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
||||
{loading ? <LoadingSpinner /> : null}
|
||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||
{data ? (
|
||||
|
||||
@@ -44,7 +44,7 @@ function JobReconciliationModalContainer({ reconciliationModal, toggleModalVisib
|
||||
onOk={handleCancel}
|
||||
onCancel={handleCancel}
|
||||
cancelButtonProps={{ display: "none" }}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
className="imex-reconciliation-modal"
|
||||
>
|
||||
{loading && <LoadingSpinner loading={loading} />}
|
||||
|
||||
@@ -24,7 +24,8 @@ export default function JobWatcherToggleComponent({
|
||||
handleToggleSelf,
|
||||
handleRemoveWatcher,
|
||||
handleWatcherSelect,
|
||||
handleTeamSelect
|
||||
handleTeamSelect,
|
||||
isEmployee
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -66,22 +67,32 @@ export default function JobWatcherToggleComponent({
|
||||
<List>
|
||||
<List.Item
|
||||
actions={[
|
||||
<Button
|
||||
type={isWatching ? "primary" : "default"}
|
||||
danger={!isWatching}
|
||||
icon={isWatching ? <EyeOutlined /> : <EyeFilled />}
|
||||
size="medium"
|
||||
onClick={handleToggleSelf}
|
||||
loading={adding || removing}
|
||||
>
|
||||
{isWatching ? t("notifications.labels.unwatch") : t("notifications.labels.watch")}
|
||||
</Button>
|
||||
<Tooltip title={!isEmployee ? t("notifications.tooltips.not-employee") : ""} placement="top">
|
||||
<span>
|
||||
<Button
|
||||
type={isWatching ? "primary" : "default"}
|
||||
danger={!isWatching}
|
||||
icon={isWatching ? <EyeOutlined /> : <EyeFilled />}
|
||||
size="medium"
|
||||
onClick={handleToggleSelf}
|
||||
loading={adding || removing}
|
||||
disabled={!isEmployee || adding || removing}
|
||||
>
|
||||
{isWatching ? t("notifications.labels.unwatch") : t("notifications.labels.watch")}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
]}
|
||||
>
|
||||
<List.Item.Meta>
|
||||
<Text type="secondary" style={{ marginBottom: 8, display: "block" }}>
|
||||
{t("notifications.labels.watching-issue")}
|
||||
</Text>
|
||||
{!isEmployee && (
|
||||
<Text type="danger" style={{ marginBottom: 8, display: "block" }}>
|
||||
{t("notifications.tooltips.not-employee")}
|
||||
</Text>
|
||||
)}
|
||||
</List.Item.Meta>
|
||||
</List.Item>
|
||||
</List>
|
||||
@@ -98,8 +109,11 @@ export default function JobWatcherToggleComponent({
|
||||
<EmployeeSearchSelectComponent
|
||||
style={{ minWidth: "100%" }}
|
||||
options={
|
||||
bodyshop?.employees?.filter((e) =>
|
||||
jobWatchers.every((w) => w.user_email !== e.user_email && e.active && e.user_email)
|
||||
bodyshop?.employees?.filter(
|
||||
(e) =>
|
||||
e.user_email && // Ensure user_email is not null or undefined
|
||||
e.active && // Ensure employee is active
|
||||
jobWatchers.every((w) => w.user_email !== e.user_email) // Ensure not already a watcher
|
||||
) || []
|
||||
}
|
||||
placeholder={t("notifications.labels.employee-search")}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import JobWatcherToggleComponent from "./job-watcher-toggle.component.jsx";
|
||||
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -21,13 +22,14 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const userEmail = currentUser.email;
|
||||
const jobid = job.id;
|
||||
|
||||
const isEmployee = useIsEmployee(bodyshop, currentUser);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedWatcher, setSelectedWatcher] = useState(null);
|
||||
const [selectedTeam, setSelectedTeam] = useState(null);
|
||||
|
||||
const userEmail = currentUser.email;
|
||||
const jobid = job.id;
|
||||
|
||||
// Fetch current watchers with refetch capability
|
||||
const {
|
||||
data: watcherData,
|
||||
@@ -139,13 +141,13 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
});
|
||||
|
||||
const handleToggleSelf = useCallback(async () => {
|
||||
if (adding || removing) return;
|
||||
if (adding || removing || !isEmployee) return;
|
||||
if (isWatching) {
|
||||
await removeWatcher({ variables: { jobid, userEmail } });
|
||||
} else {
|
||||
await addWatcher({ variables: { jobid, userEmail } });
|
||||
}
|
||||
}, [isWatching, addWatcher, removeWatcher, jobid, userEmail, adding, removing]);
|
||||
}, [isWatching, addWatcher, removeWatcher, jobid, userEmail, adding, removing, isEmployee]);
|
||||
|
||||
const handleRemoveWatcher = useCallback(
|
||||
async (email) => {
|
||||
@@ -187,7 +189,16 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
setSelectedTeam(null);
|
||||
return;
|
||||
}
|
||||
await Promise.all(newWatchers.map((email) => addWatcher({ variables: { jobid, userEmail: email } })));
|
||||
await Promise.all(
|
||||
newWatchers.map((email) =>
|
||||
addWatcher({
|
||||
variables: {
|
||||
jobid,
|
||||
userEmail: email
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
[jobWatchers, addWatcher, jobid, adding]
|
||||
);
|
||||
@@ -212,6 +223,7 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
||||
handleWatcherSelect={handleWatcherSelect}
|
||||
handleTeamSelect={handleTeamSelect}
|
||||
currentUser={currentUser}
|
||||
isEmployee={isEmployee} // Pass isEmployee to the component
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import { Col, Row } from "antd";
|
||||
import Axios from "axios";
|
||||
import _ from "lodash";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import {
|
||||
DELETE_AVAILABLE_JOB,
|
||||
@@ -33,7 +34,6 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
|
||||
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
|
||||
import HeaderFields from "./jobs-available-supplement.headerfields";
|
||||
import JobsAvailableTableComponent from "./jobs-available-table.component";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -195,7 +195,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
|
||||
|
||||
await deleteJob({
|
||||
variables: { id: estData.id }
|
||||
}).then((r) => {
|
||||
}).then(() => {
|
||||
refetch();
|
||||
setInsertLoading(false);
|
||||
});
|
||||
@@ -315,7 +315,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
|
||||
|
||||
deleteJob({
|
||||
variables: { id: estData.id }
|
||||
}).then((r) => {
|
||||
}).then(() => {
|
||||
refetch();
|
||||
setInsertLoading(false);
|
||||
});
|
||||
@@ -372,7 +372,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
|
||||
loadEstData({ variables: { id: record.id } });
|
||||
modalSearchState[1](record.clm_no);
|
||||
setJobModalVisible(true);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -456,7 +456,7 @@ function replaceEmpty(someObj, replaceValue = null) {
|
||||
return JSON.parse(temp);
|
||||
}
|
||||
|
||||
async function CheckTaxRatesUSA(estData, bodyshop) {
|
||||
async function CheckTaxRatesUSA(estData) {
|
||||
if (!estData.parts_tax_rates?.PAM) {
|
||||
estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC;
|
||||
}
|
||||
@@ -568,7 +568,7 @@ async function CheckTaxRates(estData, bodyshop) {
|
||||
});
|
||||
//}
|
||||
}
|
||||
function ResolveCCCLineIssues(estData, bodyshop) {
|
||||
function ResolveCCCLineIssues(estData) {
|
||||
//Find all misc amounts, populate them to the act price.
|
||||
//This needs to be done before cleansing unq_seq since some misc prices could move over.
|
||||
estData.joblines.data.forEach((line) => {
|
||||
@@ -585,6 +585,9 @@ function ResolveCCCLineIssues(estData, bodyshop) {
|
||||
// line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`;
|
||||
line.mod_lbr_ty = "LAR";
|
||||
}
|
||||
if (line.mod_lbr_ty === "OTSL") {
|
||||
line.mod_lbr_ty = line.mod_lbr_hrs === 0 ? null : "LAB";
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -65,7 +65,7 @@ export default connect(
|
||||
<Modal
|
||||
title={t("jobs.labels.existing_jobs")}
|
||||
width={"80%"}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
okButtonProps={{ disabled: selectedJob ? false : true }}
|
||||
{...modalProps}
|
||||
>
|
||||
|
||||
@@ -20,7 +20,14 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")),
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
export function NoteUpsertModalContainer({ currentUser, noteUpsertModal, toggleModalVisible, insertAuditTrail }) {
|
||||
@@ -123,7 +130,7 @@ export function NoteUpsertModalContainer({ currentUser, noteUpsertModal, toggleM
|
||||
onCancel={() => {
|
||||
toggleModalVisible();
|
||||
}}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form form={form} onFinish={handleFinish} layout="vertical">
|
||||
<NoteUpsertModalComponent form={form} />
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { Badge, Button, Space, Spin, Switch, Tooltip, Typography } from "antd";
|
||||
import { Alert, Badge, Button, Space, Spin, Switch, Tooltip, Typography } from "antd";
|
||||
import { CheckCircleFilled, CheckCircleOutlined, EyeFilled, EyeOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import "./notification-center.styles.scss";
|
||||
import day from "../../utils/day.js";
|
||||
import { forwardRef, useRef, useEffect } from "react";
|
||||
import { forwardRef, useEffect, useRef } from "react";
|
||||
import { DateTimeFormat } from "../../utils/DateFormatter.jsx";
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
@@ -26,7 +26,8 @@ const NotificationCenterComponent = forwardRef(
|
||||
markAllRead,
|
||||
loadMore,
|
||||
onNotificationClick,
|
||||
unreadCount
|
||||
unreadCount,
|
||||
isEmployee
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
@@ -93,7 +94,12 @@ const NotificationCenterComponent = forwardRef(
|
||||
) : (
|
||||
<EyeOutlined className="notification-toggle-icon" />
|
||||
)}
|
||||
<Switch checked={showUnreadOnly} onChange={(checked) => toggleUnreadOnly(checked)} size="small" />
|
||||
<Switch
|
||||
checked={showUnreadOnly}
|
||||
onChange={(checked) => toggleUnreadOnly(checked)}
|
||||
size="small"
|
||||
disabled={!isEmployee}
|
||||
/>
|
||||
</Space>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("notifications.labels.mark-all-read")}>
|
||||
@@ -106,14 +112,20 @@ const NotificationCenterComponent = forwardRef(
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
style={{ height: "400px", width: "100%" }}
|
||||
data={notifications}
|
||||
totalCount={notifications.length}
|
||||
endReached={loadMore}
|
||||
itemContent={renderNotification}
|
||||
/>
|
||||
{!isEmployee ? (
|
||||
<div style={{ padding: 10 }}>
|
||||
<Alert message={t("notifications.labels.employee-notification")} type="warning" />
|
||||
</div>
|
||||
) : (
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
style={{ height: "400px", width: "100%" }}
|
||||
data={notifications}
|
||||
totalCount={notifications.length}
|
||||
endReached={loadMore}
|
||||
itemContent={renderNotification}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import { connect } from "react-redux";
|
||||
import NotificationCenterComponent from "./notification-center.component";
|
||||
import { GET_NOTIFICATIONS } from "../../graphql/notifications.queries";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||
import day from "../../utils/day.js";
|
||||
import { INITIAL_NOTIFICATIONS, useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
||||
|
||||
// This will be used to poll for notifications when the socket is disconnected
|
||||
const NOTIFICATION_POLL_INTERVAL_SECONDS = 60;
|
||||
@@ -17,17 +18,18 @@ const NOTIFICATION_POLL_INTERVAL_SECONDS = 60;
|
||||
* @param onClose
|
||||
* @param bodyshop
|
||||
* @param unreadCount
|
||||
* @param currentUser
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }) => {
|
||||
const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount, currentUser }) => {
|
||||
const [showUnreadOnly, setShowUnreadOnly] = useState(false);
|
||||
const [notifications, setNotifications] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { isConnected, markNotificationRead, markAllNotificationsRead } = useSocket();
|
||||
const notificationRef = useRef(null);
|
||||
|
||||
const userAssociationId = bodyshop?.associations?.[0]?.id;
|
||||
const isEmployee = useIsEmployee(bodyshop, currentUser);
|
||||
|
||||
const baseWhereClause = useMemo(() => {
|
||||
return { associationid: { _eq: userAssociationId } };
|
||||
@@ -51,7 +53,7 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
||||
fetchPolicy: "cache-and-network",
|
||||
notifyOnNetworkStatusChange: true,
|
||||
pollInterval: isConnected ? 0 : day.duration(NOTIFICATION_POLL_INTERVAL_SECONDS, "seconds").asMilliseconds(),
|
||||
skip: !userAssociationId,
|
||||
skip: !userAssociationId || !isEmployee,
|
||||
onError: (err) => {
|
||||
console.error(`Error polling Notifications: ${err?.message || ""}`);
|
||||
setTimeout(() => refetch(), day.duration(2, "seconds").asMilliseconds());
|
||||
@@ -71,7 +73,7 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
||||
}, [visible, onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.notifications) {
|
||||
if (data?.notifications && isEmployee) {
|
||||
const processedNotifications = data.notifications
|
||||
.map((notif) => {
|
||||
let scenarioText;
|
||||
@@ -101,11 +103,13 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
||||
})
|
||||
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||
setNotifications(processedNotifications);
|
||||
} else if (!isEmployee) {
|
||||
setNotifications([]); // Clear notifications if not an employee
|
||||
}
|
||||
}, [data]);
|
||||
}, [data, isEmployee]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
if (!queryLoading && data?.notifications.length) {
|
||||
if (!queryLoading && data?.notifications.length && isEmployee) {
|
||||
setIsLoading(true); // Show spinner during fetchMore
|
||||
fetchMore({
|
||||
variables: { offset: data.notifications.length, where: whereClause },
|
||||
@@ -121,13 +125,14 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
||||
})
|
||||
.finally(() => setIsLoading(false)); // Hide spinner when done
|
||||
}
|
||||
}, [data?.notifications?.length, fetchMore, queryLoading, whereClause]);
|
||||
}, [data?.notifications?.length, fetchMore, queryLoading, whereClause, isEmployee]);
|
||||
|
||||
const handleToggleUnreadOnly = (value) => {
|
||||
setShowUnreadOnly(value);
|
||||
};
|
||||
|
||||
const handleMarkAllRead = useCallback(() => {
|
||||
if (!isEmployee) return; // Do nothing if not an employee
|
||||
setIsLoading(true);
|
||||
markAllNotificationsRead()
|
||||
.then(() => {
|
||||
@@ -147,7 +152,7 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
||||
})
|
||||
.catch((e) => console.error(`Error marking all notifications read: ${e?.message || ""}`))
|
||||
.finally(() => setIsLoading(false));
|
||||
}, [markAllNotificationsRead, userAssociationId, showUnreadOnly]);
|
||||
}, [markAllNotificationsRead, userAssociationId, showUnreadOnly, isEmployee]);
|
||||
|
||||
const handleNotificationClick = useCallback(
|
||||
(notificationId) => {
|
||||
@@ -170,17 +175,18 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible && !isConnected) {
|
||||
if (visible && !isConnected && isEmployee) {
|
||||
setIsLoading(true);
|
||||
refetch()
|
||||
.catch((err) => console.error(`Error re-fetching notifications: ${err?.message || ""}`))
|
||||
.finally(() => setIsLoading(false));
|
||||
}
|
||||
}, [visible, isConnected, refetch]);
|
||||
}, [visible, isConnected, refetch, isEmployee]);
|
||||
|
||||
return (
|
||||
<NotificationCenterComponent
|
||||
ref={notificationRef}
|
||||
isEmployee={isEmployee}
|
||||
visible={visible}
|
||||
onClose={onClose}
|
||||
notifications={notifications}
|
||||
@@ -196,7 +202,8 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(NotificationCenterContainer);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button, Card, Checkbox, Divider, Form, Space, Switch, Table, Typography } from "antd";
|
||||
import { Alert, Button, Card, Checkbox, Divider, Form, Space, Switch, Table, Typography } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import {
|
||||
QUERY_NOTIFICATION_SETTINGS,
|
||||
@@ -16,14 +16,16 @@ import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx";
|
||||
import PropTypes from "prop-types";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import ColumnHeaderCheckbox from "../notification-settings/column-header-checkbox.component.jsx";
|
||||
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
||||
|
||||
/**
|
||||
* Notifications Settings Form
|
||||
* @param currentUser
|
||||
* @param bodyshop
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const NotificationSettingsForm = ({ currentUser }) => {
|
||||
const NotificationSettingsForm = ({ currentUser, bodyshop }) => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [initialValues, setInitialValues] = useState({});
|
||||
@@ -31,6 +33,7 @@ const NotificationSettingsForm = ({ currentUser }) => {
|
||||
const [autoAddEnabled, setAutoAddEnabled] = useState(false);
|
||||
const [initialAutoAdd, setInitialAutoAdd] = useState(false);
|
||||
const notification = useNotification();
|
||||
const isEmployee = useIsEmployee(bodyshop, currentUser);
|
||||
|
||||
// Fetch notification settings and notifications_autoadd
|
||||
const { loading, error, data } = useQuery(QUERY_NOTIFICATION_SETTINGS, {
|
||||
@@ -199,6 +202,11 @@ const NotificationSettingsForm = ({ currentUser }) => {
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
{!isEmployee && (
|
||||
<div style={{ width: "100%", marginBottom: "10px" }}>
|
||||
<Alert message={t("notifications.labels.employee-notification")} type="warning" />
|
||||
</div>
|
||||
)}
|
||||
<Table dataSource={dataSource} columns={columns} pagination={false} bordered rowKey="key" />
|
||||
<Divider />
|
||||
</Card>
|
||||
@@ -209,11 +217,13 @@ const NotificationSettingsForm = ({ currentUser }) => {
|
||||
NotificationSettingsForm.propTypes = {
|
||||
currentUser: PropTypes.shape({
|
||||
email: PropTypes.string.isRequired
|
||||
}).isRequired
|
||||
}).isRequired,
|
||||
bodyshop: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser
|
||||
currentUser: selectCurrentUser,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(NotificationSettingsForm);
|
||||
|
||||
@@ -333,7 +333,7 @@ export function PartsOrderModalContainer({
|
||||
onOk={() => form.submit()}
|
||||
okButtonProps={{ loading: saving }}
|
||||
cancelButtonProps={{ loading: saving }}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
width="75%"
|
||||
forceRender
|
||||
>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function PartsQueueDetailCard() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer open={!!selected} destroyOnClose width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
||||
<Drawer open={!!selected} destroyOnHidden width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
||||
{loading ? <LoadingSpinner /> : null}
|
||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||
{data ? (
|
||||
|
||||
@@ -90,7 +90,7 @@ export function PartsReceiveModalContainer({ partsReceiveModal, toggleModalVisib
|
||||
onCancel={() => toggleModalVisible()}
|
||||
onOk={() => form.submit()}
|
||||
okButtonProps={{ loading: loading }}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
forceRender
|
||||
width="50%"
|
||||
>
|
||||
|
||||
@@ -134,7 +134,7 @@ function PaymentModalContainer({ paymentModal, toggleModalVisible, bodyshop }) {
|
||||
<Modal
|
||||
title={!context || (context && !context.id) ? t("payments.labels.new") : t("payments.labels.edit")}
|
||||
open={open}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
okText={t("general.actions.save")}
|
||||
onOk={() => form.submit()}
|
||||
width="50%"
|
||||
|
||||
@@ -32,7 +32,7 @@ export function PrintCenterModalContainer({ printCenterModal, toggleModalVisible
|
||||
okText={t("general.actions.close")}
|
||||
width="90%"
|
||||
title={t("printcenter.labels.title")}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
>
|
||||
<PrintCenterModalComponent context={context} />
|
||||
</Modal>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { Card, Col, Form, Radio, Row } from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../../redux/user/user.selectors";
|
||||
import { HasFeatureAccess } from "../../feature-wrapper/feature-wrapper.component";
|
||||
|
||||
const LayoutSettings = ({ t }) => (
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const LayoutSettings = ({ t, bodyshop }) => (
|
||||
<Card title={t("production.settings.layout")} style={{ maxWidth: "100%", overflowX: "auto" }}>
|
||||
<Row gutter={[16, 16]}>
|
||||
{[
|
||||
@@ -30,14 +38,18 @@ const LayoutSettings = ({ t }) => (
|
||||
{ value: false, label: t("production.labels.wide") }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "cardcolor",
|
||||
label: t("production.labels.cardcolor"),
|
||||
options: [
|
||||
{ value: true, label: t("production.labels.on") },
|
||||
{ value: false, label: t("production.labels.off") }
|
||||
]
|
||||
},
|
||||
...(HasFeatureAccess({ bodyshop, featureName: "smartscheduling" })
|
||||
? [
|
||||
{
|
||||
name: "cardcolor",
|
||||
label: t("production.labels.cardcolor"),
|
||||
options: [
|
||||
{ value: true, label: t("production.labels.on") },
|
||||
{ value: false, label: t("production.labels.off") }
|
||||
]
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: "kiosk",
|
||||
label: t("production.labels.kiosk_mode"),
|
||||
@@ -67,4 +79,4 @@ LayoutSettings.propTypes = {
|
||||
t: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default LayoutSettings;
|
||||
export default connect(mapStateToProps)(LayoutSettings);
|
||||
|
||||
@@ -28,7 +28,7 @@ export function ReportCenterModalContainer({ reportCenterModal, toggleModalVisib
|
||||
onOk={() => toggleModalVisible()}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
cancelButtonProps={{ style: { display: "none" } }}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
width="80%"
|
||||
>
|
||||
<RbacWrapperComponent action="shop:reportcenter">
|
||||
|
||||
@@ -209,7 +209,7 @@ export function ScheduleJobModalContainer({
|
||||
onOk={() => form.submit()}
|
||||
width={"90%"}
|
||||
maskClosable={false}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
okButtonProps={{
|
||||
loading: loading
|
||||
}}
|
||||
|
||||
@@ -106,7 +106,7 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
|
||||
<>
|
||||
<Modal
|
||||
open={state.open}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
width="80%"
|
||||
closable={false}
|
||||
cancelButtonProps={{ style: { display: "none" } }}
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function ShopInfoNotificationsAutoadd({ bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Filter employee options to ensure active employees with valid IDs
|
||||
const employeeOptions = bodyshop?.employees?.filter((e) => e.active && e.id && typeof e.id === "string") || [];
|
||||
const employeeOptions = bodyshop?.employees?.filter((e) => e.active && e.user_email && e.id) || [];
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -275,7 +275,7 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
|
||||
toggleModalVisible();
|
||||
}}
|
||||
okButtonProps={{ disabled: !isTouched }}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
|
||||
@@ -70,7 +70,7 @@ export function TechLookupJobsDrawer({ bodyshop, setPrintCenterContext }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer open={!!selected} destroyOnClose width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
||||
<Drawer open={!!selected} destroyOnHidden width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
||||
{loading ? <LoadingSpinner /> : null}
|
||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||
{data ? (
|
||||
|
||||
@@ -39,7 +39,7 @@ export function TimeTicketListTeamPay({ bodyshop, context, actions }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal width={"80%"} open={visible} destroyOnClose onOk={handleOk} onCancel={() => setVisible(false)}>
|
||||
<Modal width={"80%"} open={visible} destroyOnHidden onOk={handleOk} onCancel={() => setVisible(false)}>
|
||||
<Form layout="vertical" form={form} initialValues={{ jobid: jobId }}>
|
||||
<LayoutFormRow grow noDivider>
|
||||
<Form.Item shouldUpdate>
|
||||
|
||||
@@ -181,7 +181,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
|
||||
)}
|
||||
</Space>
|
||||
}
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
id="time-ticket-modal"
|
||||
>
|
||||
<Form
|
||||
|
||||
@@ -119,7 +119,7 @@ export function TimeTickeTaskModalContainer({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
destroyOnClose
|
||||
destroyOnHidden
|
||||
open={open}
|
||||
onCancel={() => {
|
||||
toggleModalVisible();
|
||||
|
||||
@@ -2474,7 +2474,8 @@
|
||||
"teams-search": "Search for a Team",
|
||||
"unwatch": "Unwatch",
|
||||
"watch": "Watch",
|
||||
"watching-issue": "Watching"
|
||||
"watching-issue": "Watching",
|
||||
"employee-notification": "Notifications are disabled because you do not have an associated Employee record."
|
||||
},
|
||||
"scenarios": {
|
||||
"alternate-transport-changed": "Alternate Transport Changed",
|
||||
@@ -2494,7 +2495,9 @@
|
||||
"tasks-updated-created": "Tasks Updated / Created"
|
||||
},
|
||||
"tooltips": {
|
||||
"job-watchers": "Job Watchers"
|
||||
"job-watchers": "Job Watchers",
|
||||
"not-employee": "You need to be an employee to watch this job. Reach out to your admin to get set up!",
|
||||
"not-employee-notifications": "You must be an employee to receive notifications"
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
|
||||
@@ -2476,7 +2476,8 @@
|
||||
"teams-search": "",
|
||||
"unwatch": "",
|
||||
"watch": "",
|
||||
"watching-issue": ""
|
||||
"watching-issue": "",
|
||||
"employee-notification": ""
|
||||
},
|
||||
"scenarios": {
|
||||
"alternate-transport-changed": "",
|
||||
@@ -2496,7 +2497,8 @@
|
||||
"tasks-updated-created": ""
|
||||
},
|
||||
"tooltips": {
|
||||
"job-watchers": ""
|
||||
"job-watchers": "",
|
||||
"not-employee": ""
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
|
||||
@@ -2476,7 +2476,8 @@
|
||||
"teams-search": "",
|
||||
"unwatch": "",
|
||||
"watch": "",
|
||||
"watching-issue": ""
|
||||
"watching-issue": "",
|
||||
"employee-notification": ""
|
||||
},
|
||||
"scenarios": {
|
||||
"alternate-transport-changed": "",
|
||||
@@ -2496,7 +2497,8 @@
|
||||
"tasks-updated-created": ""
|
||||
},
|
||||
"tooltips": {
|
||||
"job-watchers": ""
|
||||
"job-watchers": "",
|
||||
"not-employee": ""
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
|
||||
19
client/src/utils/useIsEmployee.js
Normal file
19
client/src/utils/useIsEmployee.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
/**
|
||||
* Check if the user is an employee of the bodyshop
|
||||
* @param bodyshop
|
||||
* @param userOrEmail
|
||||
* @returns {boolean|*}
|
||||
*/
|
||||
export function useIsEmployee(bodyshop, userOrEmail) {
|
||||
return useMemo(() => {
|
||||
if (!bodyshop || !bodyshop.employees) return false;
|
||||
|
||||
// Handle both user object and email string
|
||||
const email = typeof userOrEmail === "string" ? userOrEmail : userOrEmail?.email;
|
||||
if (!email) return false;
|
||||
|
||||
return bodyshop.employees.some((employee) => employee.user_email === email);
|
||||
}, [bodyshop, userOrEmail]);
|
||||
}
|
||||
667
package-lock.json
generated
667
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -16,14 +16,14 @@
|
||||
"job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.804.0",
|
||||
"@aws-sdk/client-elasticache": "^3.804.0",
|
||||
"@aws-sdk/client-s3": "^3.804.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.804.0",
|
||||
"@aws-sdk/client-ses": "^3.804.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.804.0",
|
||||
"@aws-sdk/lib-storage": "^3.804.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.804.0",
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.808.0",
|
||||
"@aws-sdk/client-elasticache": "^3.808.0",
|
||||
"@aws-sdk/client-s3": "^3.808.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.808.0",
|
||||
"@aws-sdk/client-ses": "^3.808.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.808.0",
|
||||
"@aws-sdk/lib-storage": "^3.808.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.808.0",
|
||||
"@opensearch-project/opensearch": "^2.13.0",
|
||||
"@socket.io/admin-ui": "^0.5.1",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
@@ -42,7 +42,7 @@
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.1",
|
||||
"firebase-admin": "^13.2.0",
|
||||
"firebase-admin": "^13.4.0",
|
||||
"graphql": "^16.11.0",
|
||||
"graphql-request": "^6.1.0",
|
||||
"intuit-oauth": "^4.2.0",
|
||||
@@ -57,7 +57,7 @@
|
||||
"node-persist": "^4.0.4",
|
||||
"nodemailer": "^6.10.0",
|
||||
"phone": "^3.1.58",
|
||||
"query-string": "^9.1.2",
|
||||
"query-string": "7.1.3",
|
||||
"recursive-diff": "^1.0.9",
|
||||
"rimraf": "^6.0.1",
|
||||
"skia-canvas": "^2.0.2",
|
||||
@@ -65,7 +65,7 @@
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-adapter": "^2.5.5",
|
||||
"ssh2-sftp-client": "^11.0.0",
|
||||
"twilio": "^5.6.0",
|
||||
"twilio": "^5.6.1",
|
||||
"uuid": "^11.1.0",
|
||||
"winston": "^3.17.0",
|
||||
"winston-cloudwatch": "^6.3.0",
|
||||
@@ -80,7 +80,7 @@
|
||||
"mock-require": "^3.0.3",
|
||||
"p-limit": "^3.1.0",
|
||||
"prettier": "^3.5.3",
|
||||
"supertest": "^7.1.0",
|
||||
"supertest": "^7.1.1",
|
||||
"vitest": "^3.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ exports.chatter = require("./chatter").default;
|
||||
exports.claimscorp = require("./claimscorp").default;
|
||||
exports.kaizen = require("./kaizen").default;
|
||||
exports.usageReport = require("./usageReport").default;
|
||||
exports.podium = require("./podium").default;
|
||||
exports.podium = require("./podium").default;
|
||||
exports.emsUpload = require("./emsUpload").default;
|
||||
22
server/data/emsUpload.js
Normal file
22
server/data/emsUpload.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const moment = require("moment-timezone");
|
||||
const logger = require("../utils/logger");
|
||||
const s3Client = require("../utils/s3"); // Using the S3 client utilities with LocalStack support
|
||||
|
||||
const emsUpload = async (req, res) => {
|
||||
try {
|
||||
const { bodyshopid, ciecaid, clm_no, ownr_ln } = req.body;
|
||||
const presignedUrl = await s3Client.getPresignedUrl({
|
||||
bucketName: process.env.S3_EMS_UPLOAD_BUCKET,
|
||||
key: `${bodyshopid}/${ciecaid}-${clm_no}-${ownr_ln}-${moment().format("YYYY-MM-DD--HH-mm-ss")}.zip`
|
||||
});
|
||||
res.status(200).json({ presignedUrl });
|
||||
} catch (error) {
|
||||
logger.log("ems-upload-presign-error", "ERROR", req?.user?.email, null, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
res.status(500).json({ error: error.message, stack: error.stack });
|
||||
}
|
||||
};
|
||||
|
||||
exports.default = emsUpload;
|
||||
@@ -185,7 +185,7 @@ async function uploadViaSFTP(csvObj) {
|
||||
await sftp.connect(ftpSetup);
|
||||
|
||||
try {
|
||||
csvObj.result = await sftp.put(Buffer.from(csvObj.xml), `${csvObj.filename}`);
|
||||
csvObj.result = await sftp.put(Buffer.from(csvObj.csv), `${csvObj.filename}`);
|
||||
logger.log("podium-sftp-upload", "DEBUG", "api", csvObj.bodyshopid, {
|
||||
imexshopid: csvObj.imexshopid,
|
||||
filename: csvObj.filename,
|
||||
|
||||
@@ -138,6 +138,9 @@ router.post("/canvastest", validateFirebaseIdTokenMiddleware, canvastest);
|
||||
// Alert Check
|
||||
router.post("/alertcheck", eventAuthorizationMiddleware, alertCheck);
|
||||
|
||||
//EMS Upload
|
||||
router.post("/emsupload", validateFirebaseIdTokenMiddleware, data.emsUpload);
|
||||
|
||||
// Redis Cache Routes
|
||||
router.post("/bodyshop-cache", eventAuthorizationMiddleware, updateBodyshopCache);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ const {
|
||||
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||
const { InstanceRegion } = require("./instanceMgr");
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
|
||||
|
||||
const createS3Client = () => {
|
||||
const S3Options = {
|
||||
@@ -95,6 +96,17 @@ const createS3Client = () => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const getPresignedUrl = async ({ bucketName, key }) => {
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: key,
|
||||
StorageClass: "INTELLIGENT_TIERING"
|
||||
});
|
||||
const presignedUrl = await getSignedUrl(s3Client, command, { expiresIn: 360 });
|
||||
return presignedUrl;
|
||||
}
|
||||
|
||||
return {
|
||||
uploadFileToS3,
|
||||
downloadFileFromS3,
|
||||
@@ -102,8 +114,12 @@ const createS3Client = () => {
|
||||
deleteFileFromS3,
|
||||
copyFileInS3,
|
||||
fileExistsInS3,
|
||||
getPresignedUrl,
|
||||
...s3Client
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = createS3Client();
|
||||
|
||||
Reference in New Issue
Block a user