Merged in release/2025-06-02 (pull request #2330)

Release/2025 06 02
This commit is contained in:
Patrick Fic
2025-05-22 16:46:27 +00:00
54 changed files with 1259 additions and 1332 deletions

355
client/package-lock.json generated
View File

@@ -13,19 +13,19 @@
"@apollo/client": "^3.13.6", "@apollo/client": "^3.13.6",
"@emotion/is-prop-valid": "^1.3.1", "@emotion/is-prop-valid": "^1.3.1",
"@fingerprintjs/fingerprintjs": "^4.6.1", "@fingerprintjs/fingerprintjs": "^4.6.1",
"@firebase/analytics": "^0.10.13", "@firebase/analytics": "^0.10.16",
"@firebase/app": "^0.12.1", "@firebase/app": "^0.13.0",
"@firebase/auth": "^1.10.2", "@firebase/auth": "^1.10.5",
"@firebase/firestore": "^4.7.12", "@firebase/firestore": "^4.7.15",
"@firebase/messaging": "^0.12.18", "@firebase/messaging": "^0.12.21",
"@jsreport/browser-client": "^3.1.0", "@jsreport/browser-client": "^3.1.0",
"@reduxjs/toolkit": "^2.8.1", "@reduxjs/toolkit": "^2.8.2",
"@sentry/cli": "^2.45.0", "@sentry/cli": "^2.45.0",
"@sentry/react": "^9.18.0", "@sentry/react": "^9.22.0",
"@sentry/vite-plugin": "^3.4.0", "@sentry/vite-plugin": "^3.5.0",
"@splitsoftware/splitio-react": "^2.1.1", "@splitsoftware/splitio-react": "^2.1.1",
"@tanem/react-nprogress": "^5.0.53", "@tanem/react-nprogress": "^5.0.53",
"antd": "^5.25.1", "antd": "^5.25.2",
"apollo-link-logger": "^2.0.1", "apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^4.3.0", "apollo-link-sentry": "^4.3.0",
"autosize": "^6.0.1", "autosize": "^6.0.1",
@@ -49,7 +49,7 @@
"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.2", "query-string": "^9.2.0",
"raf-schd": "^4.0.3", "raf-schd": "^4.0.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-big-calendar": "^1.18.0", "react-big-calendar": "^1.18.0",
@@ -78,7 +78,7 @@
"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.88.0", "sass": "^1.89.0",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
"styled-components": "^6.1.18", "styled-components": "^6.1.18",
"subscriptions-transport-ws": "^0.11.0", "subscriptions-transport-ws": "^0.11.0",
@@ -90,12 +90,12 @@
"@ant-design/icons": "^6.0.0", "@ant-design/icons": "^6.0.0",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-react": "^7.27.1", "@babel/preset-react": "^7.27.1",
"@dotenvx/dotenvx": "^1.44.0", "@dotenvx/dotenvx": "^1.44.1",
"@emotion/babel-plugin": "^11.13.5", "@emotion/babel-plugin": "^11.13.5",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@eslint/js": "^9.26.0", "@eslint/js": "^9.27.0",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.51.1",
"@sentry/webpack-plugin": "^3.4.0", "@sentry/webpack-plugin": "^3.5.0",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
@@ -108,7 +108,7 @@
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"globals": "^15.15.0", "globals": "^15.15.0",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
"memfs": "^4.17.1", "memfs": "^4.17.2",
"os-browserify": "^0.3.0", "os-browserify": "^0.3.0",
"playwright": "^1.51.1", "playwright": "^1.51.1",
"react-error-overlay": "^6.1.0", "react-error-overlay": "^6.1.0",
@@ -120,7 +120,7 @@
"vite-plugin-node-polyfills": "^0.23.0", "vite-plugin-node-polyfills": "^0.23.0",
"vite-plugin-pwa": "^1.0.0", "vite-plugin-pwa": "^1.0.0",
"vite-plugin-style-import": "^2.0.0", "vite-plugin-style-import": "^2.0.0",
"vitest": "^3.1.3", "vitest": "^3.1.4",
"workbox-window": "^7.3.0" "workbox-window": "^7.3.0"
}, },
"engines": { "engines": {
@@ -2587,9 +2587,9 @@
} }
}, },
"node_modules/@dotenvx/dotenvx": { "node_modules/@dotenvx/dotenvx": {
"version": "1.44.0", "version": "1.44.1",
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.44.0.tgz", "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.44.1.tgz",
"integrity": "sha512-18Aa+7KP/L2Kj9lxmT4EJZnsCq/xGIHgzU26rdzsKMhjpeT3YY+qin/dNAnIaVHPZnee7kXpZL55M9htd30r7Q==", "integrity": "sha512-j1QImCqf/XJmhIjC1OPpgiZV9g370HG9MNT9s/CDwCKsoYzNCPEKK+GfsidahJx7yIlBbm+4dPLlGec+bKn7oA==",
"dev": true, "dev": true,
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"dependencies": { "dependencies": {
@@ -2912,13 +2912,16 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.26.0", "version": "9.27.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz",
"integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
} }
}, },
"node_modules/@fingerprintjs/fingerprintjs": { "node_modules/@fingerprintjs/fingerprintjs": {
@@ -2931,15 +2934,15 @@
} }
}, },
"node_modules/@firebase/analytics": { "node_modules/@firebase/analytics": {
"version": "0.10.13", "version": "0.10.16",
"resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.13.tgz", "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.16.tgz",
"integrity": "sha512-X+6wMOPgA9l0AeeMdMcMfaCP4XKPvrhx55MGuMrfHvUrOvFKldpzBum7KkoGJMoexKmqmKP+mCmJMY9Fb8K6Hw==", "integrity": "sha512-cMtp19He7Fd6uaj/nDEul+8JwvJsN8aRSJyuA1QN3QrKvfDDp+efjVurJO61sJpkVftw9O9nNMdhFbRcTmTfRQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.14", "@firebase/component": "0.6.17",
"@firebase/installations": "0.6.14", "@firebase/installations": "0.6.17",
"@firebase/logger": "0.4.4", "@firebase/logger": "0.4.4",
"@firebase/util": "1.11.1", "@firebase/util": "1.12.0",
"tslib": "^2.1.0" "tslib": "^2.1.0"
}, },
"peerDependencies": { "peerDependencies": {
@@ -2947,14 +2950,14 @@
} }
}, },
"node_modules/@firebase/app": { "node_modules/@firebase/app": {
"version": "0.12.1", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.12.1.tgz", "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.0.tgz",
"integrity": "sha512-ASExOlmmjRMdwOQ65Oj6R9JBqa7iiT1/LgZjtbU7FqxoJZNWHrt39NJ/z2bjyYDdAHX8jkY7muFqzahScCXgfA==", "integrity": "sha512-Vj3MST245nq+V5UmmfEkB3isIgPouyUr8yGJlFeL9Trg/umG5ogAvrjAYvQ8gV7daKDoQSRnJKWI2JFpQqRsuQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.14", "@firebase/component": "0.6.17",
"@firebase/logger": "0.4.4", "@firebase/logger": "0.4.4",
"@firebase/util": "1.11.1", "@firebase/util": "1.12.0",
"idb": "7.1.1", "idb": "7.1.1",
"tslib": "^2.1.0" "tslib": "^2.1.0"
}, },
@@ -2963,14 +2966,14 @@
} }
}, },
"node_modules/@firebase/auth": { "node_modules/@firebase/auth": {
"version": "1.10.2", "version": "1.10.5",
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.2.tgz", "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.5.tgz",
"integrity": "sha512-HHudcj3CJyXpoMKslNOVHGSNJdAUjvy5xBA/G/uPb32QFqvx5F3EW9RDYvve2IHEN7Vpc1QTkk/28J32x83UGA==", "integrity": "sha512-6wF/NdMTwObL4RNQePunuzMr9O3gyftisvFZFFKf57D2HONXo87YymogRV8d+Z7SLA0rcNBN1gLJVk2D0y97gA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.14", "@firebase/component": "0.6.17",
"@firebase/logger": "0.4.4", "@firebase/logger": "0.4.4",
"@firebase/util": "1.11.1", "@firebase/util": "1.12.0",
"tslib": "^2.1.0" "tslib": "^2.1.0"
}, },
"engines": { "engines": {
@@ -2987,12 +2990,12 @@
} }
}, },
"node_modules/@firebase/component": { "node_modules/@firebase/component": {
"version": "0.6.14", "version": "0.6.17",
"resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.14.tgz", "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.17.tgz",
"integrity": "sha512-kf/zAT8GQJ9nYoHuj0mv7twp1QzifKYrO+GsmsVHHM+Hi9KkmI7E3B3J0CtihHpb34vinl4gbJrYJ2p2wfvc9A==", "integrity": "sha512-M6DOg7OySrKEFS8kxA3MU5/xc37fiOpKPMz6cTsMUcsuKB6CiZxxNAvgFta8HGRgEpZbi8WjGIj6Uf+TpOhyzg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/util": "1.11.1", "@firebase/util": "1.12.0",
"tslib": "^2.1.0" "tslib": "^2.1.0"
}, },
"engines": { "engines": {
@@ -3000,14 +3003,14 @@
} }
}, },
"node_modules/@firebase/firestore": { "node_modules/@firebase/firestore": {
"version": "4.7.12", "version": "4.7.15",
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.12.tgz", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.15.tgz",
"integrity": "sha512-50KRdSp8xA7+G0wfWxlnCoEN951mt8BVdLMxeP57Rehj2DqIb41q6Fc6JH0dfQ4TlMqWua1YfVY1jPEAaHVF9w==", "integrity": "sha512-FgWTmkNBEXdKCoN2ngBNjrMaXuBx6QwjiZZVnOGg+VjUmiBq5gAqlDIW5bZY6i/NYvLUrWugdqIs7y9GHEqwww==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.14", "@firebase/component": "0.6.17",
"@firebase/logger": "0.4.4", "@firebase/logger": "0.4.4",
"@firebase/util": "1.11.1", "@firebase/util": "1.12.0",
"@firebase/webchannel-wrapper": "1.0.3", "@firebase/webchannel-wrapper": "1.0.3",
"@grpc/grpc-js": "~1.9.0", "@grpc/grpc-js": "~1.9.0",
"@grpc/proto-loader": "^0.7.8", "@grpc/proto-loader": "^0.7.8",
@@ -3021,13 +3024,13 @@
} }
}, },
"node_modules/@firebase/installations": { "node_modules/@firebase/installations": {
"version": "0.6.14", "version": "0.6.17",
"resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.14.tgz", "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.17.tgz",
"integrity": "sha512-uE837g9+sv6PfjWPgOfG3JtjZ+hJ7KBHO4UVenVsvhzgOxFkvLjO/bgE7fyvsaD3fOHSXunx3adRIg4eUEMPyA==", "integrity": "sha512-zfhqCNJZRe12KyADtRrtOj+SeSbD1H/K8J24oQAJVv/u02eQajEGlhZtcx9Qk7vhGWF5z9dvIygVDYqLL4o1XQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.14", "@firebase/component": "0.6.17",
"@firebase/util": "1.11.1", "@firebase/util": "1.12.0",
"idb": "7.1.1", "idb": "7.1.1",
"tslib": "^2.1.0" "tslib": "^2.1.0"
}, },
@@ -3048,15 +3051,15 @@
} }
}, },
"node_modules/@firebase/messaging": { "node_modules/@firebase/messaging": {
"version": "0.12.18", "version": "0.12.21",
"resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.18.tgz", "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.21.tgz",
"integrity": "sha512-2MGhUGoCZloB7ysoYzG/T2nnRmHYLT+AcqYouZuD6APabpkDhF8lHsmSQq4MFSlXhI3DKFOXxjuvbY8ec4C2JQ==", "integrity": "sha512-bYJ2Evj167Z+lJ1ach6UglXz5dUKY1zrJZd15GagBUJSR7d9KfiM1W8dsyL0lDxcmhmA/sLaBYAAhF1uilwN0g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.14", "@firebase/component": "0.6.17",
"@firebase/installations": "0.6.14", "@firebase/installations": "0.6.17",
"@firebase/messaging-interop-types": "0.2.3", "@firebase/messaging-interop-types": "0.2.3",
"@firebase/util": "1.11.1", "@firebase/util": "1.12.0",
"idb": "7.1.1", "idb": "7.1.1",
"tslib": "^2.1.0" "tslib": "^2.1.0"
}, },
@@ -3071,9 +3074,9 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@firebase/util": { "node_modules/@firebase/util": {
"version": "1.11.1", "version": "1.12.0",
"resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.11.1.tgz", "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.0.tgz",
"integrity": "sha512-RXg4WE8C2LUrvoV/TMGRTu223zZf9Dq9MR8yHZio9nF9TpLnpCPURw9VWWB2WATDl6HfIdWfl2x2SJYtHkN4hw==", "integrity": "sha512-Z4rK23xBCwgKDqmzGVMef+Vb4xso2j5Q8OG0vVL4m4fA5ZjPMYQazu8OJJC3vtQRC3SQ/Pgx/6TPNVsCd70QRw==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -3836,9 +3839,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@reduxjs/toolkit": { "node_modules/@reduxjs/toolkit": {
"version": "2.8.1", "version": "2.8.2",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.1.tgz", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz",
"integrity": "sha512-GLjHS13LiBdiuxSJvfWs3+Cx5yt97mCbuVlDteTusS6VRksPhoWviO8L1e3Re1G94m6lkw/l4pjEEyyNaGf19g==", "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@standard-schema/spec": "^1.0.0", "@standard-schema/spec": "^1.0.0",
@@ -4458,88 +4461,88 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@sentry-internal/browser-utils": { "node_modules/@sentry-internal/browser-utils": {
"version": "9.18.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.18.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.22.0.tgz",
"integrity": "sha512-TwSlmgYpHhe55JpOcVApkM0XcXZh1/cYuEPKPFgeaaPD8BrQrLJJvwKxnonSWXOhdnkJxi4GgK7j7mw57PS4aA==", "integrity": "sha512-Ou1tBnVxFAIn8i9gvrWzRotNJQYiu3awNXpsFCw6qFwmiKAVPa6b13vCdolhXnrIiuR77jY1LQnKh9hXpoRzsg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "9.18.0" "@sentry/core": "9.22.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/feedback": { "node_modules/@sentry-internal/feedback": {
"version": "9.18.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.18.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.22.0.tgz",
"integrity": "sha512-QlrB8oQK+5bfhbgK6yHF6rLwLNJ9XuGblTc51yVkm4d4jn4W/HDyaNqMfQF+JXdTiFatl8oz2xdKR8kGK8kXyg==", "integrity": "sha512-zgMVkoC61fgi41zLcSZA59vOtKxcLrKBo1ECYhPD1hxEaneNqY5fhXDwlQBw96P5l2yqkgfX6YZtSdU4ejI9yA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "9.18.0" "@sentry/core": "9.22.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/replay": { "node_modules/@sentry-internal/replay": {
"version": "9.18.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.18.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.22.0.tgz",
"integrity": "sha512-2A32FFwrlZtdpBruvpcLEfucu6BpyqOk3F4Bo5smM/5q7u0pa7q5d9FSY5l3nwKEAFAoLGv3hcCb+8wxMm50xA==", "integrity": "sha512-9GOycoKbrclcRXfcbNV8svbmAsOS5R4wXBQmKF4pFLkmFA/lJv9kdZSNYkRvkrxdNfbMIJXP+DV9EqTZcryXig==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/browser-utils": "9.18.0", "@sentry-internal/browser-utils": "9.22.0",
"@sentry/core": "9.18.0" "@sentry/core": "9.22.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/replay-canvas": { "node_modules/@sentry-internal/replay-canvas": {
"version": "9.18.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.18.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.22.0.tgz",
"integrity": "sha512-3DEyQLmHcYgcwJ8n8eMhI6bhhawPuMc2xTT+Az8gXMqCO/X9ZACpipAmhXFjYP9Ptl+w0Vh3nllJw+gXc/DOsg==", "integrity": "sha512-EcG9IMSEalFe49kowBTJObWjof/iHteDwpyuAszsFDdQUYATrVUtwpwN7o52vDYWJud4arhjrQnMamIGxa79eQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/replay": "9.18.0", "@sentry-internal/replay": "9.22.0",
"@sentry/core": "9.18.0" "@sentry/core": "9.22.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry/babel-plugin-component-annotate": { "node_modules/@sentry/babel-plugin-component-annotate": {
"version": "3.4.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.4.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-3.5.0.tgz",
"integrity": "sha512-tSzfc3aE7m0PM0Aj7HBDet5llH9AB9oc+tBQ8AvOqUSnWodLrNCuWeQszJ7mIBovD3figgCU3h0cvI6U5cDtsg==", "integrity": "sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
} }
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "9.18.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.18.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.22.0.tgz",
"integrity": "sha512-0SWfp4J2+mH4lZOcHfyIwt9VoGD7yCGQE1cm0BPcLwKnrVQeXHtUXNYNy8HTHSjTGyoFDhEAYelE/tdA3OLcWQ==", "integrity": "sha512-3TeRm74dvX0JdjX0AgkQa+22iUHwHnY+Q6M05NZ+tDeCNHGK/mEBTeqquS1oQX67jWyuvYmG3VV6RJUxtG9Paw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/browser-utils": "9.18.0", "@sentry-internal/browser-utils": "9.22.0",
"@sentry-internal/feedback": "9.18.0", "@sentry-internal/feedback": "9.22.0",
"@sentry-internal/replay": "9.18.0", "@sentry-internal/replay": "9.22.0",
"@sentry-internal/replay-canvas": "9.18.0", "@sentry-internal/replay-canvas": "9.22.0",
"@sentry/core": "9.18.0" "@sentry/core": "9.22.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry/bundler-plugin-core": { "node_modules/@sentry/bundler-plugin-core": {
"version": "3.4.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.4.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-3.5.0.tgz",
"integrity": "sha512-X1Q41AsQ6xcT6hB4wYmBDBukndKM/inT4IsR7pdKLi7ICpX2Qq6lisamBAEPCgEvnLpazSFguaiC0uiwMKAdqw==", "integrity": "sha512-zDzPrhJqAAy2VzV4g540qAZH4qxzisstK2+NIJPZUUKztWRWUV2cMHsyUtdctYgloGkLyGpZJBE3RE6dmP/xqQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/core": "^7.18.5", "@babel/core": "^7.18.5",
"@sentry/babel-plugin-component-annotate": "3.4.0", "@sentry/babel-plugin-component-annotate": "3.5.0",
"@sentry/cli": "2.42.2", "@sentry/cli": "2.42.2",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"find-up": "^5.0.0", "find-up": "^5.0.0",
@@ -4899,22 +4902,22 @@
} }
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "9.18.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.18.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.22.0.tgz",
"integrity": "sha512-kRVH8BqMiaU2FTHYa68zNlAloS43jl4XtIEHkLKVH/7gUtwRmM4Gqj8P7RTrZdO1Lo7ksYnGj+AG05Z09CRbOw==", "integrity": "sha512-ixvtKmPF42Y6ckGUbFlB54OWI75H2gO5UYHojO6eXFpS7xO3ZGgV/QH6wb40mWK+0w5XZ0233FuU9VpsuE6mKA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry/react": { "node_modules/@sentry/react": {
"version": "9.18.0", "version": "9.22.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.18.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.22.0.tgz",
"integrity": "sha512-1cCLYZrZ2gu6H8eE83DC47kLf+pzD1Rim3dDoOEvwt1F5cD3K/DBeIhoHZaXqBeQxuVyHXOOLXSAC/CIuas5Aw==", "integrity": "sha512-mI43NnioBYdG5TiXqRlhV1feZs9bnrrl+k5HOHBK7VQtymaXO0fkcsRLZTkdSgLRLMJGasZuvVhq2xK+18QyWQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/browser": "9.18.0", "@sentry/browser": "9.22.0",
"@sentry/core": "9.18.0", "@sentry/core": "9.22.0",
"hoist-non-react-statics": "^3.3.2" "hoist-non-react-statics": "^3.3.2"
}, },
"engines": { "engines": {
@@ -4925,12 +4928,12 @@
} }
}, },
"node_modules/@sentry/vite-plugin": { "node_modules/@sentry/vite-plugin": {
"version": "3.4.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-3.4.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-3.5.0.tgz",
"integrity": "sha512-pUFBGrKsHuc8K6A7B1wU2nx65n9aIzvTlcHX9yZ1qvjEO0cZFih0JCwu1Fcav/yrtT9RMN44L/ugu/kMBHQhjQ==", "integrity": "sha512-jUnpTdpicG8wefamw7eNo2uO+Q3KCbOAiF76xH4gfNHSW6TN2hBfOtmLu7J+ive4c0Al3+NEHz19bIPR0lkwWg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/bundler-plugin-core": "3.4.0", "@sentry/bundler-plugin-core": "3.5.0",
"unplugin": "1.0.1" "unplugin": "1.0.1"
}, },
"engines": { "engines": {
@@ -4938,13 +4941,13 @@
} }
}, },
"node_modules/@sentry/webpack-plugin": { "node_modules/@sentry/webpack-plugin": {
"version": "3.4.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-3.4.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-3.5.0.tgz",
"integrity": "sha512-i+nAxxniJV5ovijojjTF5n+Yj08Xk8my+vm8+oo0C0I7xcnI2gOKft6B0sJOq01CNbo85X5m/3/edL0PKoWE9w==", "integrity": "sha512-xvclj0QY2HyU7uJLzOlHSrZQBDwfnGKJxp8mmlU4L7CwmK+8xMCqlO7tYZoqE4K/wU3c2xpXql70x8qmvNMxzQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/bundler-plugin-core": "3.4.0", "@sentry/bundler-plugin-core": "3.5.0",
"unplugin": "1.0.1", "unplugin": "1.0.1",
"uuid": "^9.0.0" "uuid": "^9.0.0"
}, },
@@ -5810,14 +5813,14 @@
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.4.tgz",
"integrity": "sha512-7FTQQuuLKmN1Ig/h+h/GO+44Q1IlglPlR2es4ab7Yvfx+Uk5xsv+Ykk+MEt/M2Yn/xGmzaLKxGw2lgy2bwuYqg==", "integrity": "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "3.1.3", "@vitest/spy": "3.1.4",
"@vitest/utils": "3.1.3", "@vitest/utils": "3.1.4",
"chai": "^5.2.0", "chai": "^5.2.0",
"tinyrainbow": "^2.0.0" "tinyrainbow": "^2.0.0"
}, },
@@ -5826,13 +5829,13 @@
} }
}, },
"node_modules/@vitest/mocker": { "node_modules/@vitest/mocker": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.4.tgz",
"integrity": "sha512-PJbLjonJK82uCWHjzgBJZuR7zmAOrSvKk1QBxrennDIgtH4uK0TB1PvYmc0XBCigxxtiAVPfWtAdy4lpz8SQGQ==", "integrity": "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/spy": "3.1.3", "@vitest/spy": "3.1.4",
"estree-walker": "^3.0.3", "estree-walker": "^3.0.3",
"magic-string": "^0.30.17" "magic-string": "^0.30.17"
}, },
@@ -5873,9 +5876,9 @@
} }
}, },
"node_modules/@vitest/pretty-format": { "node_modules/@vitest/pretty-format": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz",
"integrity": "sha512-i6FDiBeJUGLDKADw2Gb01UtUNb12yyXAqC/mmRWuYl+m/U9GS7s8us5ONmGkGpUUo7/iAYzI2ePVfOZTYvUifA==", "integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -5886,13 +5889,13 @@
} }
}, },
"node_modules/@vitest/runner": { "node_modules/@vitest/runner": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.4.tgz",
"integrity": "sha512-Tae+ogtlNfFei5DggOsSUvkIaSuVywujMj6HzR97AHK6XK8i3BuVyIifWAm/sE3a15lF5RH9yQIrbXYuo0IFyA==", "integrity": "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/utils": "3.1.3", "@vitest/utils": "3.1.4",
"pathe": "^2.0.3" "pathe": "^2.0.3"
}, },
"funding": { "funding": {
@@ -5907,13 +5910,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vitest/snapshot": { "node_modules/@vitest/snapshot": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.4.tgz",
"integrity": "sha512-XVa5OPNTYUsyqG9skuUkFzAeFnEzDp8hQu7kZ0N25B1+6KjGm4hWLtURyBbsIAOekfWQ7Wuz/N/XXzgYO3deWQ==", "integrity": "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "3.1.3", "@vitest/pretty-format": "3.1.4",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"pathe": "^2.0.3" "pathe": "^2.0.3"
}, },
@@ -5939,9 +5942,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vitest/spy": { "node_modules/@vitest/spy": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.4.tgz",
"integrity": "sha512-x6w+ctOEmEXdWaa6TO4ilb7l9DxPR5bwEb6hILKuxfU1NqWT2mpJD9NJN7t3OTfxmVlOMrvtoFJGdgyzZ605lQ==", "integrity": "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -5952,13 +5955,13 @@
} }
}, },
"node_modules/@vitest/utils": { "node_modules/@vitest/utils": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.3.tgz", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.4.tgz",
"integrity": "sha512-2Ltrpht4OmHO9+c/nmHtF09HWiyWdworqnHIwjfvDyWjuwKbdkcS9AnhsDn+8E2RM4x++foD1/tNuLPVvWG1Rg==", "integrity": "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/pretty-format": "3.1.3", "@vitest/pretty-format": "3.1.4",
"loupe": "^3.1.3", "loupe": "^3.1.3",
"tinyrainbow": "^2.0.0" "tinyrainbow": "^2.0.0"
}, },
@@ -6088,9 +6091,9 @@
} }
}, },
"node_modules/antd": { "node_modules/antd": {
"version": "5.25.1", "version": "5.25.2",
"resolved": "https://registry.npmjs.org/antd/-/antd-5.25.1.tgz", "resolved": "https://registry.npmjs.org/antd/-/antd-5.25.2.tgz",
"integrity": "sha512-4KC7KuPCjr0z3Vuw9DsF+ceqJaPLbuUI3lOX1sY8ix25ceamp+P8yxOmk3Y2JHCD2ZAhq+5IQ/DTJRN2adWYKQ==", "integrity": "sha512-7R2nUvlHhey7Trx64+hCtGXOiy+DTUs1Lv5bwbV1LzEIZIhWb0at1AM6V3K108a5lyoR9n7DX3ptlLF7uYV/DQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ant-design/colors": "^7.2.0", "@ant-design/colors": "^7.2.0",
@@ -6128,11 +6131,11 @@
"rc-rate": "~2.13.1", "rc-rate": "~2.13.1",
"rc-resize-observer": "^1.4.3", "rc-resize-observer": "^1.4.3",
"rc-segmented": "~2.7.0", "rc-segmented": "~2.7.0",
"rc-select": "~14.16.7", "rc-select": "~14.16.8",
"rc-slider": "~11.1.8", "rc-slider": "~11.1.8",
"rc-steps": "~6.0.1", "rc-steps": "~6.0.1",
"rc-switch": "~4.1.0", "rc-switch": "~4.1.0",
"rc-table": "~7.50.4", "rc-table": "~7.50.5",
"rc-tabs": "~15.6.1", "rc-tabs": "~15.6.1",
"rc-textarea": "~1.10.0", "rc-textarea": "~1.10.0",
"rc-tooltip": "~6.4.0", "rc-tooltip": "~6.4.0",
@@ -11833,9 +11836,9 @@
} }
}, },
"node_modules/memfs": { "node_modules/memfs": {
"version": "4.17.1", "version": "4.17.2",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.1.tgz", "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.17.2.tgz",
"integrity": "sha512-thuTRd7F4m4dReCIy7vv4eNYnU6XI/tHMLSMMHLiortw/Y0QxqKtinG523U2aerzwYWGi606oBP4oMPy4+edag==", "integrity": "sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
@@ -13569,9 +13572,9 @@
} }
}, },
"node_modules/query-string": { "node_modules/query-string": {
"version": "9.1.2", "version": "9.2.0",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-9.1.2.tgz", "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.2.0.tgz",
"integrity": "sha512-s3UlTyjxRux4KjwWaJsjh1Mp8zoCkSGKirbD9H89pEM9UOZsfpRZpdfzvsy2/mGlLfC3NnYVpy2gk7jXITHEtA==", "integrity": "sha512-YIRhrHujoQxhexwRLxfy3VSjOXmvZRd2nyw1PwL1UUqZ/ys1dEZd1+NSgXkne2l/4X/7OXkigEAuhTX0g/ivJQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"decode-uri-component": "^0.4.1", "decode-uri-component": "^0.4.1",
@@ -14024,9 +14027,9 @@
} }
}, },
"node_modules/rc-select": { "node_modules/rc-select": {
"version": "14.16.7", "version": "14.16.8",
"resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.7.tgz", "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz",
"integrity": "sha512-lT9kO5gFHQdJzu9a0btcOtNaJHkhenSl8H5mcpgXN9VIMXP59rnkpbdHmPrteixWs1D5zFOTyoTYX3b7joADIQ==", "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.10.1", "@babel/runtime": "^7.10.1",
@@ -14097,9 +14100,9 @@
} }
}, },
"node_modules/rc-table": { "node_modules/rc-table": {
"version": "7.50.4", "version": "7.50.5",
"resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.4.tgz", "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.50.5.tgz",
"integrity": "sha512-Y+YuncnQqoS5e7yHvfvlv8BmCvwDYDX/2VixTBEhkMDk9itS9aBINp4nhzXFKiBP/frG4w0pS9d9Rgisl0T1Bw==", "integrity": "sha512-FDZu8aolhSYd3v9KOc3lZOVAU77wmRRu44R0Wfb8Oj1dXRUsloFaXMSl6f7yuWZUxArJTli7k8TEOX2mvhDl4A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.10.1", "@babel/runtime": "^7.10.1",
@@ -15375,9 +15378,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.88.0", "version": "1.89.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.88.0.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.0.tgz",
"integrity": "sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==", "integrity": "sha512-ld+kQU8YTdGNjOLfRWBzewJpU5cwEv/h5yyqlSeJcj6Yh8U4TDA9UA5FPicqDz/xgRPWRSYIQNiFks21TbA9KQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chokidar": "^4.0.0", "chokidar": "^4.0.0",
@@ -17533,9 +17536,9 @@
} }
}, },
"node_modules/vite-node": { "node_modules/vite-node": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.3.tgz", "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.4.tgz",
"integrity": "sha512-uHV4plJ2IxCl4u1up1FQRrqclylKAogbtBfOTwcuJ28xFi+89PZ57BRh+naIRvH70HPwxy5QHYzg1OrEaC7AbA==", "integrity": "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -17731,19 +17734,19 @@
} }
}, },
"node_modules/vitest": { "node_modules/vitest": {
"version": "3.1.3", "version": "3.1.4",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.3.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz",
"integrity": "sha512-188iM4hAHQ0km23TN/adso1q5hhwKqUpv+Sd6p5sOuh6FhQnRNW3IsiIpvxqahtBabsJ2SLZgmGSpcYK4wQYJw==", "integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vitest/expect": "3.1.3", "@vitest/expect": "3.1.4",
"@vitest/mocker": "3.1.3", "@vitest/mocker": "3.1.4",
"@vitest/pretty-format": "^3.1.3", "@vitest/pretty-format": "^3.1.4",
"@vitest/runner": "3.1.3", "@vitest/runner": "3.1.4",
"@vitest/snapshot": "3.1.3", "@vitest/snapshot": "3.1.4",
"@vitest/spy": "3.1.3", "@vitest/spy": "3.1.4",
"@vitest/utils": "3.1.3", "@vitest/utils": "3.1.4",
"chai": "^5.2.0", "chai": "^5.2.0",
"debug": "^4.4.0", "debug": "^4.4.0",
"expect-type": "^1.2.1", "expect-type": "^1.2.1",
@@ -17756,7 +17759,7 @@
"tinypool": "^1.0.2", "tinypool": "^1.0.2",
"tinyrainbow": "^2.0.0", "tinyrainbow": "^2.0.0",
"vite": "^5.0.0 || ^6.0.0", "vite": "^5.0.0 || ^6.0.0",
"vite-node": "3.1.3", "vite-node": "3.1.4",
"why-is-node-running": "^2.3.0" "why-is-node-running": "^2.3.0"
}, },
"bin": { "bin": {
@@ -17772,8 +17775,8 @@
"@edge-runtime/vm": "*", "@edge-runtime/vm": "*",
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
"@vitest/browser": "3.1.3", "@vitest/browser": "3.1.4",
"@vitest/ui": "3.1.3", "@vitest/ui": "3.1.4",
"happy-dom": "*", "happy-dom": "*",
"jsdom": "*" "jsdom": "*"
}, },

View File

@@ -12,19 +12,19 @@
"@apollo/client": "^3.13.6", "@apollo/client": "^3.13.6",
"@emotion/is-prop-valid": "^1.3.1", "@emotion/is-prop-valid": "^1.3.1",
"@fingerprintjs/fingerprintjs": "^4.6.1", "@fingerprintjs/fingerprintjs": "^4.6.1",
"@firebase/analytics": "^0.10.13", "@firebase/analytics": "^0.10.16",
"@firebase/app": "^0.12.1", "@firebase/app": "^0.13.0",
"@firebase/auth": "^1.10.2", "@firebase/auth": "^1.10.5",
"@firebase/firestore": "^4.7.12", "@firebase/firestore": "^4.7.15",
"@firebase/messaging": "^0.12.18", "@firebase/messaging": "^0.12.21",
"@jsreport/browser-client": "^3.1.0", "@jsreport/browser-client": "^3.1.0",
"@reduxjs/toolkit": "^2.8.1", "@reduxjs/toolkit": "^2.8.2",
"@sentry/cli": "^2.45.0", "@sentry/cli": "^2.45.0",
"@sentry/react": "^9.18.0", "@sentry/react": "^9.22.0",
"@sentry/vite-plugin": "^3.4.0", "@sentry/vite-plugin": "^3.5.0",
"@splitsoftware/splitio-react": "^2.1.1", "@splitsoftware/splitio-react": "^2.1.1",
"@tanem/react-nprogress": "^5.0.53", "@tanem/react-nprogress": "^5.0.53",
"antd": "^5.25.1", "antd": "^5.25.2",
"apollo-link-logger": "^2.0.1", "apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^4.3.0", "apollo-link-sentry": "^4.3.0",
"autosize": "^6.0.1", "autosize": "^6.0.1",
@@ -48,7 +48,7 @@
"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.2", "query-string": "^9.2.0",
"raf-schd": "^4.0.3", "raf-schd": "^4.0.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-big-calendar": "^1.18.0", "react-big-calendar": "^1.18.0",
@@ -77,7 +77,7 @@
"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.88.0", "sass": "^1.89.0",
"socket.io-client": "^4.8.1", "socket.io-client": "^4.8.1",
"styled-components": "^6.1.18", "styled-components": "^6.1.18",
"subscriptions-transport-ws": "^0.11.0", "subscriptions-transport-ws": "^0.11.0",
@@ -130,12 +130,12 @@
"@ant-design/icons": "^6.0.0", "@ant-design/icons": "^6.0.0",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-react": "^7.27.1", "@babel/preset-react": "^7.27.1",
"@dotenvx/dotenvx": "^1.44.0", "@dotenvx/dotenvx": "^1.44.1",
"@emotion/babel-plugin": "^11.13.5", "@emotion/babel-plugin": "^11.13.5",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@eslint/js": "^9.26.0", "@eslint/js": "^9.27.0",
"@playwright/test": "^1.51.1", "@playwright/test": "^1.51.1",
"@sentry/webpack-plugin": "^3.4.0", "@sentry/webpack-plugin": "^3.5.0",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
@@ -148,7 +148,7 @@
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"globals": "^15.15.0", "globals": "^15.15.0",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",
"memfs": "^4.17.1", "memfs": "^4.17.2",
"os-browserify": "^0.3.0", "os-browserify": "^0.3.0",
"playwright": "^1.51.1", "playwright": "^1.51.1",
"react-error-overlay": "^6.1.0", "react-error-overlay": "^6.1.0",
@@ -160,7 +160,7 @@
"vite-plugin-node-polyfills": "^0.23.0", "vite-plugin-node-polyfills": "^0.23.0",
"vite-plugin-pwa": "^1.0.0", "vite-plugin-pwa": "^1.0.0",
"vite-plugin-style-import": "^2.0.0", "vite-plugin-style-import": "^2.0.0",
"vitest": "^3.1.3", "vitest": "^3.1.4",
"workbox-window": "^7.3.0" "workbox-window": "^7.3.0"
} }
} }

View File

@@ -34,16 +34,14 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
SubscribeToTopicForFCMNotification(); SubscribeToTopicForFCMNotification();
//Register WS handlers // Register WebSocket handlers
if (socket && socket.connected) { if (socket && socket.connected) {
registerMessagingHandlers({ socket, client }); registerMessagingHandlers({ socket, client });
}
return () => { return () => {
if (socket && socket.connected) {
unregisterMessagingHandlers({ socket }); unregisterMessagingHandlers({ socket });
} };
}; }
}, [bodyshop, socket, t, client]); }, [bodyshop, socket, t, client]);
if (!bodyshop || !bodyshop.messagingservicesid) return <></>; if (!bodyshop || !bodyshop.messagingservicesid) return <></>;

View File

@@ -1,5 +1,5 @@
import { Badge, Card, List, Space, Tag } from "antd"; import { Badge, Card, List, Space, Tag } from "antd";
import React, { useEffect, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Virtuoso } from "react-virtuoso"; import { Virtuoso } from "react-virtuoso";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -10,35 +10,60 @@ import PhoneFormatter from "../../utils/PhoneFormatter";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import _ from "lodash"; import _ from "lodash";
import "./chat-conversation-list.styles.scss"; import "./chat-conversation-list.styles.scss";
import { useQuery } from "@apollo/client";
import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries.js";
import { phone } from "phone";
import { useTranslation } from "react-i18next";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation selectedConversation: selectSelectedConversation,
bodyshop: selectBodyshop
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId)) setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId))
}); });
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation }) { function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) {
// That comma is there for a reason, do not remove it const { t } = useTranslation();
const [, forceUpdate] = useState(false); const [, forceUpdate] = useState(false);
// Re-render every minute const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""));
const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
variables: {
bodyshopid: bodyshop.id,
phone_numbers: phoneNumbers
},
skip: !conversationList.length,
fetchPolicy: "cache-and-network"
});
const optOutMap = useMemo(() => {
const map = new Map();
optOutData?.phone_number_opt_out?.forEach((optOut) => {
map.set(optOut.phone_number, true);
});
return map;
}, [optOutData?.phone_number_opt_out]);
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
forceUpdate((prev) => !prev); // Toggle state to trigger re-render forceUpdate((prev) => !prev);
}, 60000); // 1 minute in milliseconds }, 60000);
return () => clearInterval(interval);
return () => clearInterval(interval); // Cleanup on unmount
}, []); }, []);
// Memoize the sorted conversation list const sortedConversationList = useMemo(() => {
const sortedConversationList = React.useMemo(() => {
return _.orderBy(conversationList, ["updated_at"], ["desc"]); return _.orderBy(conversationList, ["updated_at"], ["desc"]);
}, [conversationList]); }, [conversationList]);
const renderConversation = (index) => { const renderConversation = (index, t) => {
const item = sortedConversationList[index]; const item = sortedConversationList[index];
const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "");
const hasOptOutEntry = optOutMap.has(normalizedPhone);
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>; const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
const cardContentLeft = const cardContentLeft =
item.job_conversations.length > 0 item.job_conversations.length > 0
@@ -60,7 +85,12 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
</> </>
); );
const cardExtra = <Badge count={item.messages_aggregate.aggregate.count} />; const cardExtra = (
<>
<Badge count={item.messages_aggregate.aggregate.count} />
{hasOptOutEntry && <Tag color="red">{t("messaging.labels.no_consent")}</Tag>}
</>
);
const getCardStyle = () => const getCardStyle = () =>
item.id === selectedConversation item.id === selectedConversation
@@ -73,9 +103,25 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
onClick={() => setSelectedConversation(item.id)} onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`} className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`}
> >
<Card style={getCardStyle()} bordered={false} size="small" extra={cardExtra} title={cardTitle}> <Card style={getCardStyle()} variant={true} size="small" extra={cardExtra} title={cardTitle}>
<div style={{ display: "inline-block", width: "70%", textAlign: "left" }}>{cardContentLeft}</div> <div
<div style={{ display: "inline-block", width: "30%", textAlign: "right" }}>{cardContentRight}</div> style={{
display: "inline-block",
width: "70%",
textAlign: "left"
}}
>
{cardContentLeft}
</div>
<div
style={{
display: "inline-block",
width: "30%",
textAlign: "right"
}}
>
{cardContentRight}
</div>
</Card> </Card>
</List.Item> </List.Item>
); );
@@ -85,7 +131,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
<div className="chat-list-container"> <div className="chat-list-container">
<Virtuoso <Virtuoso
data={sortedConversationList} data={sortedConversationList}
itemContent={(index) => renderConversation(index)} itemContent={(index) => renderConversation(index, t)}
style={{ height: "100%", width: "100%" }} style={{ height: "100%", width: "100%" }}
/> />
</div> </div>

View File

@@ -24,7 +24,7 @@
/* Add spacing and better alignment for items */ /* Add spacing and better alignment for items */
.chat-list-item { .chat-list-item {
padding: 0.5rem 0; /* Add spacing between list items */ padding: 0.2rem 0; /* Add spacing between list items */
.ant-card { .ant-card {
border-radius: 8px; /* Slight rounding for card edges */ border-radius: 8px; /* Slight rounding for card edges */

View File

@@ -37,7 +37,7 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
jobId: conversation.job_conversations[0] && conversation.job_conversations[0].jobid jobId: conversation.job_conversations[0] && conversation.job_conversations[0]?.jobid
}, },
skip: !open || !conversation.job_conversations || conversation.job_conversations.length === 0 skip: !open || !conversation.job_conversations || conversation.job_conversations.length === 0
@@ -67,14 +67,14 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
<> <>
{!bodyshop.uselocalmediaserver && ( {!bodyshop.uselocalmediaserver && (
<JobsDocumentImgproxyGalleryExternal <JobsDocumentImgproxyGalleryExternal
jobId={conversation.job_conversations[0].jobid} jobId={conversation.job_conversations[0]?.jobid}
externalMediaState={[selectedMedia, setSelectedMedia]} externalMediaState={[selectedMedia, setSelectedMedia]}
/> />
)} )}
{bodyshop.uselocalmediaserver && open && ( {bodyshop.uselocalmediaserver && open && (
<JobDocumentsLocalGalleryExternal <JobDocumentsLocalGalleryExternal
externalMediaState={[selectedMedia, setSelectedMedia]} externalMediaState={[selectedMedia, setSelectedMedia]}
jobId={conversation.job_conversations[0] && conversation.job_conversations[0].jobid} jobId={conversation.job_conversations[0] && conversation.job_conversations[0]?.jobid}
/> />
)} )}
</> </>
@@ -89,7 +89,7 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
{bodyshop.uselocalmediaserver && open && ( {bodyshop.uselocalmediaserver && open && (
<JobDocumentsLocalGalleryExternal <JobDocumentsLocalGalleryExternal
externalMediaState={[selectedMedia, setSelectedMedia]} externalMediaState={[selectedMedia, setSelectedMedia]}
jobId={conversation.job_conversations[0] && conversation.job_conversations[0].jobid} jobId={conversation.job_conversations[0] && conversation.job_conversations[0]?.jobid}
/> />
)} )}
</> </>

View File

@@ -1,5 +1,5 @@
import { LoadingOutlined, SendOutlined } from "@ant-design/icons"; import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
import { Input, Spin } from "antd"; import { Alert, Input, Spin } from "antd";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -10,6 +10,9 @@ import { selectIsSending, selectMessage } from "../../redux/messaging/messaging.
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component"; import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component";
import ChatPresetsComponent from "../chat-presets/chat-presets.component"; import ChatPresetsComponent from "../chat-presets/chat-presets.component";
import { useQuery } from "@apollo/client";
import { phone } from "phone";
import { GET_PHONE_NUMBER_OPT_OUT } from "../../graphql/phone-number-opt-out.queries";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -25,16 +28,24 @@ const mapDispatchToProps = (dispatch) => ({
function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) { function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) {
const inputArea = useRef(null); const inputArea = useRef(null);
const [selectedMedia, setSelectedMedia] = useState([]); const [selectedMedia, setSelectedMedia] = useState([]);
const { t } = useTranslation();
const normalizedPhone = phone(conversation.phone_num, "CA").phoneNumber.replace(/^\+1/, "");
const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUT, {
variables: { bodyshopid: bodyshop.id, phone_number: normalizedPhone },
fetchPolicy: "cache-and-network"
});
const isOptedOut = !!optOutData?.phone_number_opt_out?.[0];
useEffect(() => { useEffect(() => {
inputArea.current.focus(); inputArea.current.focus();
}, [isSending, setMessage]); }, [isSending, setMessage]);
const { t } = useTranslation();
const handleEnter = () => { const handleEnter = () => {
const selectedImages = selectedMedia.filter((i) => i.isSelected); const selectedImages = selectedMedia.filter((i) => i.isSelected);
if ((message === "" || !message) && selectedImages.length === 0) return; if ((message === "" || !message) && selectedImages.length === 0) return;
if (isOptedOut) return; // Prevent sending if phone number is opted out
logImEXEvent("messaging_send_message"); logImEXEvent("messaging_send_message");
if (selectedImages.length < 11) { if (selectedImages.length < 11) {
@@ -44,7 +55,8 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
messagingServiceSid: bodyshop.messagingservicesid, messagingServiceSid: bodyshop.messagingservicesid,
conversationid: conversation.id, conversationid: conversation.id,
selectedMedia: selectedImages, selectedMedia: selectedImages,
imexshopid: bodyshop.imexshopid imexshopid: bodyshop.imexshopid,
bodyshopid: bodyshop.id
}; };
sendMessage(newMessage); sendMessage(newMessage);
setSelectedMedia( setSelectedMedia(
@@ -57,6 +69,7 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
return ( return (
<div className="imex-flex-row" style={{ width: "100%" }}> <div className="imex-flex-row" style={{ width: "100%" }}>
{isOptedOut && <Alert message={t("messaging.errors.no_consent")} type="warning" style={{ marginBottom: 8 }} />}
<ChatPresetsComponent className="imex-flex-row__margin" /> <ChatPresetsComponent className="imex-flex-row__margin" />
<ChatMediaSelector <ChatMediaSelector
conversation={conversation} conversation={conversation}
@@ -71,18 +84,18 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
ref={inputArea} ref={inputArea}
autoSize={{ minRows: 1, maxRows: 4 }} autoSize={{ minRows: 1, maxRows: 4 }}
value={message} value={message}
disabled={isSending} disabled={isSending || isOptedOut}
placeholder={t("messaging.labels.typeamessage")} placeholder={t("messaging.labels.typeamessage")}
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setMessage(e.target.value)}
onPressEnter={(event) => { onPressEnter={(event) => {
event.preventDefault(); event.preventDefault();
if (!!!event.shiftKey) handleEnter(); if (!event.shiftKey && !isOptedOut) handleEnter();
}} }}
/> />
</span> </span>
<SendOutlined <SendOutlined
className="chat-send-message-button" className="chat-send-message-button"
// disabled={message === "" || !message} disabled={isOptedOut || message === "" || !message}
onClick={handleEnter} onClick={handleEnter}
/> />
<Spin <Spin

View File

@@ -81,8 +81,9 @@ export function HasFeatureAccess({ featureName, bodyshop, bypass, debug = false
} }
return ( return (
bodyshop?.features?.allAccess || bodyshop?.features?.allAccess ||
bodyshop?.features?.[featureName] || (typeof bodyshop?.features?.[featureName] === "boolean"
dayjs(bodyshop?.features[featureName]).isAfter(dayjs()) ? bodyshop?.features?.[featureName]
: dayjs(bodyshop?.features?.[featureName]).isAfter(dayjs()))
); );
} }

View File

@@ -4,11 +4,12 @@ import { Col, Row } from "antd";
import Axios from "axios"; import Axios from "axios";
import _ from "lodash"; import _ from "lodash";
import queryString from "query-string"; import queryString from "query-string";
import React, { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { import {
DELETE_AVAILABLE_JOB, 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 { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
import HeaderFields from "./jobs-available-supplement.headerfields"; import HeaderFields from "./jobs-available-supplement.headerfields";
import JobsAvailableTableComponent from "./jobs-available-table.component"; import JobsAvailableTableComponent from "./jobs-available-table.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -195,7 +195,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
await deleteJob({ await deleteJob({
variables: { id: estData.id } variables: { id: estData.id }
}).then((r) => { }).then(() => {
refetch(); refetch();
setInsertLoading(false); setInsertLoading(false);
}); });
@@ -315,7 +315,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
deleteJob({ deleteJob({
variables: { id: estData.id } variables: { id: estData.id }
}).then((r) => { }).then(() => {
refetch(); refetch();
setInsertLoading(false); setInsertLoading(false);
}); });
@@ -372,7 +372,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
loadEstData({ variables: { id: record.id } }); loadEstData({ variables: { id: record.id } });
modalSearchState[1](record.clm_no); modalSearchState[1](record.clm_no);
setJobModalVisible(true); setJobModalVisible(true);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line
}, []); }, []);
useEffect(() => { useEffect(() => {
@@ -456,7 +456,7 @@ function replaceEmpty(someObj, replaceValue = null) {
return JSON.parse(temp); return JSON.parse(temp);
} }
async function CheckTaxRatesUSA(estData, bodyshop) { async function CheckTaxRatesUSA(estData) {
if (!estData.parts_tax_rates?.PAM) { if (!estData.parts_tax_rates?.PAM) {
estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC; 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. //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. //This needs to be done before cleansing unq_seq since some misc prices could move over.
estData.joblines.data.forEach((line) => { estData.joblines.data.forEach((line) => {
@@ -585,6 +585,9 @@ function ResolveCCCLineIssues(estData, bodyshop) {
// line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`; // line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`;
line.mod_lbr_ty = "LAR"; line.mod_lbr_ty = "LAR";
} }
if (line.mod_lbr_ty === "OTSL") {
line.mod_lbr_ty = line.mod_lbr_hrs === 0 ? null : "LAB";
}
} }
}); });
}); });

View File

@@ -0,0 +1,62 @@
import { useQuery } from "@apollo/client";
import { Input, Table } from "antd";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { GET_PHONE_NUMBER_OPT_OUTS } from "../../graphql/phone-number-opt-out.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapDispatchToProps = () => ({});
function PhoneNumberConsentList({ bodyshop, currentUser }) {
const { t } = useTranslation();
const [search, setSearch] = useState("");
const { loading, data } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined },
fetchPolicy: "network-only"
});
const columns = [
{
title: t("consent.phone_number"),
dataIndex: "phone_number",
render: (text) => <PhoneNumberFormatter>{text}</PhoneNumberFormatter>,
sorter: (a, b) => a.phone_number.localeCompare(b.phone_number)
},
{
title: t("consent.created_at"),
dataIndex: "created_at",
render: (text) => <TimeAgoFormatter>{text}</TimeAgoFormatter>,
sorter: (a, b) => new Date(a.created_at) - new Date(b.created_at)
}
];
return (
<div>
<Input.Search
placeholder={t("general.labels.search")}
onSearch={(value) => setSearch(value)}
style={{ marginBottom: 16 }}
/>
<Table
columns={columns}
dataSource={data?.phone_number_opt_out}
loading={loading}
rowKey="id"
style={{ marginTop: 16 }}
/>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(PhoneNumberConsentList);

View File

@@ -1,7 +1,15 @@
import { Card, Col, Form, Radio, Row } from "antd"; import { Card, Col, Form, Radio, Row } from "antd";
import PropTypes from "prop-types"; 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" }}> <Card title={t("production.settings.layout")} style={{ maxWidth: "100%", overflowX: "auto" }}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{[ {[
@@ -30,14 +38,18 @@ const LayoutSettings = ({ t }) => (
{ value: false, label: t("production.labels.wide") } { value: false, label: t("production.labels.wide") }
] ]
}, },
{ ...(HasFeatureAccess({ bodyshop, featureName: "smartscheduling" })
name: "cardcolor", ? [
label: t("production.labels.cardcolor"), {
options: [ name: "cardcolor",
{ value: true, label: t("production.labels.on") }, label: t("production.labels.cardcolor"),
{ value: false, label: t("production.labels.off") } options: [
] { value: true, label: t("production.labels.on") },
}, { value: false, label: t("production.labels.off") }
]
}
]
: []),
{ {
name: "kiosk", name: "kiosk",
label: t("production.labels.kiosk_mode"), label: t("production.labels.kiosk_mode"),
@@ -67,4 +79,4 @@ LayoutSettings.propTypes = {
t: PropTypes.func.isRequired t: PropTypes.func.isRequired
}; };
export default LayoutSettings; export default connect(mapStateToProps)(LayoutSettings);

View File

@@ -6,6 +6,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { QUERY_ACTIVE_EMPLOYEES, QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL } from "../../graphql/employees.queries"; import { QUERY_ACTIVE_EMPLOYEES, QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL } from "../../graphql/employees.queries";
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries"; import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
import { selectReportCenter } from "../../redux/modals/modals.selectors"; import { selectReportCenter } from "../../redux/modals/modals.selectors";
@@ -18,11 +19,10 @@ import EmployeeSearchSelectEmail from "../employee-search-select/employee-search
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component"; import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component"; import BlurWrapperComponent from "../feature-wrapper/blur-wrapper.component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import ReportCenterModalFiltersSortersComponent from "./report-center-modal-filters-sorters-component"; import ReportCenterModalFiltersSortersComponent from "./report-center-modal-filters-sorters-component";
import "./report-center-modal.styles.scss"; import "./report-center-modal.styles.scss";
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
reportCenterModal: selectReportCenter, reportCenterModal: selectReportCenter,
@@ -389,5 +389,7 @@ const restrictedReports = [
{ key: "job_costing_ro_date_detail", days: 183 }, { key: "job_costing_ro_date_detail", days: 183 },
{ key: "job_costing_ro_estimator", days: 183 }, { key: "job_costing_ro_estimator", days: 183 },
{ key: "job_lifecycle_date_detail", days: 183 }, { key: "job_lifecycle_date_detail", days: 183 },
{ key: "job_lifecycle_date_summary", days: 183 } { key: "job_lifecycle_date_summary", days: 183 },
{ key: "customer_list", days: 183 },
{ key: "customer_list_excel", days: 183 }
]; ];

View File

@@ -0,0 +1,25 @@
import { Typography } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import PhoneNumberConsentList from "../phone-number-consent/phone-number-consent.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({});
function ShopInfoConsentComponent({ bodyshop }) {
const { t } = useTranslation();
return (
<div>
<Typography.Title level={4}>{t("settings.title")}</Typography.Title>
{<PhoneNumberConsentList bodyshop={bodyshop} />}
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoConsentComponent);

View File

@@ -0,0 +1,28 @@
import { gql } from "@apollo/client";
export const GET_PHONE_NUMBER_OPT_OUT = gql`
query GET_PHONE_NUMBER_OPT_OUT($bodyshopid: uuid!, $phone_number: String!) {
phone_number_opt_out(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _eq: $phone_number } }) {
id
bodyshopid
phone_number
created_at
updated_at
}
}
`;
export const GET_PHONE_NUMBER_OPT_OUTS = gql`
query GET_PHONE_NUMBER_OPT_OUTS($bodyshopid: uuid!, $search: String) {
phone_number_opt_out(
where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _ilike: $search } }
order_by: [{ phone_number: asc }, { updated_at: desc }]
) {
id
bodyshopid
phone_number
created_at
updated_at
}
}
`;

View File

@@ -10,10 +10,10 @@ import ShopCsiConfig from "../../components/shop-csi-config/shop-csi-config.comp
import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container"; import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container";
import ShopInfoContainer from "../../components/shop-info/shop-info.container"; import ShopInfoContainer from "../../components/shop-info/shop-info.container";
import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component"; import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component";
import ShopInfoConsentComponent from "../../components/shop-info/shop-info.consent.component";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions"; import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component";
import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container"; import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container";
@@ -91,6 +91,14 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
children: <ShopCsiConfig /> children: <ShopCsiConfig />
}); });
} }
// Add Consent Settings tab
items.push({
key: "consent",
label: t("bodyshop.labels.consent_settings"),
children: <ShopInfoConsentComponent bodyshop={bodyshop} />
});
return ( return (
<RbacWrapper action="shop:config"> <RbacWrapper action="shop:config">
<Tabs activeKey={search.tab} onChange={(key) => history({ search: `?tab=${key}` })} items={items} /> <Tabs activeKey={search.tab} onChange={(key) => history({ search: `?tab=${key}` })} items={items} />

View File

@@ -105,7 +105,6 @@ const userReducer = (state = INITIAL_STATE, action) => {
...action.payload //Spread current user details in. ...action.payload //Spread current user details in.
} }
}; };
case UserActionTypes.SET_SHOP_DETAILS: case UserActionTypes.SET_SHOP_DETAILS:
return { return {
...state, ...state,
@@ -126,6 +125,7 @@ const userReducer = (state = INITIAL_STATE, action) => {
...state, ...state,
imexshopid: action.payload imexshopid: action.payload
}; };
default: default:
return state; return state;
} }

View File

@@ -656,6 +656,7 @@
} }
}, },
"labels": { "labels": {
"consent_settings": "Phone Number Opt-Out List",
"2tiername": "Name => RO", "2tiername": "Name => RO",
"2tiersetup": "2 Tier Setup", "2tiersetup": "2 Tier Setup",
"2tiersource": "Source => RO", "2tiersource": "Source => RO",
@@ -2377,7 +2378,8 @@
"errors": { "errors": {
"invalidphone": "The phone number is invalid. Unable to open conversation. ", "invalidphone": "The phone number is invalid. Unable to open conversation. ",
"noattachedjobs": "No Jobs have been associated to this conversation. ", "noattachedjobs": "No Jobs have been associated to this conversation. ",
"updatinglabel": "Error updating label. {{error}}" "updatinglabel": "Error updating label. {{error}}",
"no_consent": "This phone number has not consented to receive messages."
}, },
"labels": { "labels": {
"addlabel": "Add a label to this conversation.", "addlabel": "Add a label to this conversation.",
@@ -2393,7 +2395,8 @@
"selectmedia": "Select Media", "selectmedia": "Select Media",
"sentby": "Sent by {{by}} at {{time}}", "sentby": "Sent by {{by}} at {{time}}",
"typeamessage": "Send a message...", "typeamessage": "Send a message...",
"unarchive": "Unarchive" "unarchive": "Unarchive",
"no_consent": "No Consent"
}, },
"render": { "render": {
"conversation_list": "Conversation List" "conversation_list": "Conversation List"
@@ -3099,6 +3102,7 @@
"credits_not_received_date_vendorid": "Credits not Received by Vendor", "credits_not_received_date_vendorid": "Credits not Received by Vendor",
"csi": "CSI Responses", "csi": "CSI Responses",
"customer_list": "Customer List", "customer_list": "Customer List",
"customer_list_excel": "Customer List - Excel",
"cycle_time_analysis": "Cycle Time Analysis", "cycle_time_analysis": "Cycle Time Analysis",
"estimates_written_converted": "Estimates Written/Converted", "estimates_written_converted": "Estimates Written/Converted",
"estimator_detail": "Jobs by Estimator (Detail)", "estimator_detail": "Jobs by Estimator (Detail)",
@@ -3862,6 +3866,14 @@
"validation": { "validation": {
"unique_vendor_name": "You must enter a unique vendor name." "unique_vendor_name": "You must enter a unique vendor name."
} }
},
"consent": {
"phone_number": "Phone Number",
"status": "Consent Status",
"created_at": "Created At"
},
"settings": {
"title": "Phone Number Opt-Out List"
} }
} }
} }

View File

@@ -3100,6 +3100,7 @@
"credits_not_received_date_vendorid": "", "credits_not_received_date_vendorid": "",
"csi": "", "csi": "",
"customer_list": "", "customer_list": "",
"customer_list_excel": "",
"cycle_time_analysis": "", "cycle_time_analysis": "",
"estimates_written_converted": "", "estimates_written_converted": "",
"estimator_detail": "", "estimator_detail": "",

View File

@@ -3100,6 +3100,7 @@
"credits_not_received_date_vendorid": "", "credits_not_received_date_vendorid": "",
"csi": "", "csi": "",
"customer_list": "", "customer_list": "",
"customer_list_excel": "",
"cycle_time_analysis": "", "cycle_time_analysis": "",
"estimates_written_converted": "", "estimates_written_converted": "",
"estimator_detail": "", "estimator_detail": "",

View File

@@ -2004,6 +2004,18 @@ export const TemplateList = (type, context) => {
}, },
group: "customers" group: "customers"
}, },
customer_list_excel: {
title: i18n.t("reportcenter.templates.customer_list_excel"),
subject: i18n.t("reportcenter.templates.customer_list_excel"),
key: "customer_list_excel",
reporttype: "excel",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced")
},
group: "customers"
},
exported_gsr_by_ro: { exported_gsr_by_ro: {
title: i18n.t("reportcenter.templates.exported_gsr_by_ro"), title: i18n.t("reportcenter.templates.exported_gsr_by_ro"),
subject: i18n.t("reportcenter.templates.exported_gsr_by_ro"), subject: i18n.t("reportcenter.templates.exported_gsr_by_ro"),
@@ -2241,7 +2253,7 @@ export const TemplateList = (type, context) => {
field: i18n.t("bills.fields.date") field: i18n.t("bills.fields.date")
}, },
group: "purchases" group: "purchases"
}, }
} }
: {}), : {}),
...(!type || type === "courtesycarcontract" ...(!type || type === "courtesycarcontract"

View File

@@ -5861,6 +5861,32 @@
template_engine: Kriti template_engine: Kriti
url: '{{$base_url}}/opensearch' url: '{{$base_url}}/opensearch'
version: 2 version: 2
- table:
name: phone_number_opt_out
schema: public
object_relationships:
- name: bodyshop
using:
foreign_key_constraint_on: bodyshopid
select_permissions:
- role: user
permission:
columns:
- phone_number
- created_at
- updated_at
- bodyshopid
- id
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
comment: ""
- table: - table:
name: phonebook name: phonebook
schema: public schema: public

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."bodyshops" add column "enforce_sms_consent" boolean
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" add column "enforce_sms_consent" boolean
null;

View File

@@ -0,0 +1 @@
DROP TABLE "public"."phone_number_consent";

View File

@@ -0,0 +1,2 @@
CREATE TABLE "public"."phone_number_consent" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "bodyshopid" uuid NOT NULL, "phone_number" text NOT NULL, "consent_status" boolean NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "consent_updated_at" timestamptz NOT NULL DEFAULT now(), PRIMARY KEY ("id") , FOREIGN KEY ("bodyshopid") REFERENCES "public"."bodyshops"("id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"), UNIQUE ("bodyshopid", "phone_number"));
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -0,0 +1 @@
DROP TABLE "public"."phone_number_consent_history";

View File

@@ -0,0 +1,2 @@
CREATE TABLE "public"."phone_number_consent_history" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "phone_number_consent_id" uuid NOT NULL, "old_value" boolean NOT NULL, "new_value" boolean NOT NULL, "reason" text NOT NULL, "changed_at" timestamptz NOT NULL DEFAULT now(), "changed_by" text NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("phone_number_consent_id") REFERENCES "public"."phone_number_consent"("id") ON UPDATE restrict ON DELETE restrict, UNIQUE ("id"));
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -0,0 +1 @@
alter table "public"."phone_number_consent_history" alter column "old_value" set not null;

View File

@@ -0,0 +1 @@
alter table "public"."phone_number_consent_history" alter column "old_value" drop not null;

View File

@@ -0,0 +1,3 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- DROP table "public"."phone_number_consent_history";

View File

@@ -0,0 +1 @@
DROP table "public"."phone_number_consent_history";

View File

@@ -0,0 +1,2 @@
alter table "public"."phone_number_consent" alter column "consent_status" drop not null;
alter table "public"."phone_number_consent" add column "consent_status" bool;

View File

@@ -0,0 +1 @@
alter table "public"."phone_number_consent" drop column "consent_status" cascade;

View File

@@ -0,0 +1,3 @@
alter table "public"."phone_number_consent" alter column "consent_updated_at" set default now();
alter table "public"."phone_number_consent" alter column "consent_updated_at" drop not null;
alter table "public"."phone_number_consent" add column "consent_updated_at" timestamptz;

View File

@@ -0,0 +1 @@
alter table "public"."phone_number_consent" drop column "consent_updated_at" cascade;

View File

@@ -0,0 +1 @@
alter table "public"."phone_number_opt_out" rename to "phone_number_consent";

View File

@@ -0,0 +1 @@
alter table "public"."phone_number_consent" rename to "phone_number_opt_out";

View File

@@ -0,0 +1,2 @@
alter table "public"."bodyshops" alter column "enforce_sms_consent" drop not null;
alter table "public"."bodyshops" add column "enforce_sms_consent" bool;

View File

@@ -0,0 +1 @@
alter table "public"."bodyshops" drop column "enforce_sms_consent" cascade;

1535
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,14 +16,14 @@
"job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js" "job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.808.0", "@aws-sdk/client-cloudwatch-logs": "^3.812.0",
"@aws-sdk/client-elasticache": "^3.808.0", "@aws-sdk/client-elasticache": "^3.812.0",
"@aws-sdk/client-s3": "^3.808.0", "@aws-sdk/client-s3": "^3.812.0",
"@aws-sdk/client-secrets-manager": "^3.808.0", "@aws-sdk/client-secrets-manager": "^3.812.0",
"@aws-sdk/client-ses": "^3.808.0", "@aws-sdk/client-ses": "^3.812.0",
"@aws-sdk/credential-provider-node": "^3.808.0", "@aws-sdk/credential-provider-node": "^3.812.0",
"@aws-sdk/lib-storage": "^3.808.0", "@aws-sdk/lib-storage": "^3.812.0",
"@aws-sdk/s3-request-presigner": "^3.808.0", "@aws-sdk/s3-request-presigner": "^3.812.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",
@@ -31,14 +31,14 @@
"aws4": "^1.13.2", "aws4": "^1.13.2",
"axios": "^1.8.4", "axios": "^1.8.4",
"better-queue": "^3.8.12", "better-queue": "^3.8.12",
"bullmq": "^5.52.2", "bullmq": "^5.53.0",
"chart.js": "^4.4.8", "chart.js": "^4.4.8",
"cloudinary": "^2.6.1", "cloudinary": "^2.6.1",
"compression": "^1.8.0", "compression": "^1.8.0",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"cors": "2.8.5", "cors": "^2.8.5",
"crisp-status-reporter": "^1.2.2", "crisp-status-reporter": "^1.2.2",
"dd-trace": "^5.51.0", "dd-trace": "^5.52.0",
"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",
@@ -57,7 +57,7 @@
"node-persist": "^4.0.4", "node-persist": "^4.0.4",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
"phone": "^3.1.58", "phone": "^3.1.58",
"query-string": "^9.1.2", "query-string": "7.1.3",
"recursive-diff": "^1.0.9", "recursive-diff": "^1.0.9",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"skia-canvas": "^2.0.2", "skia-canvas": "^2.0.2",
@@ -73,14 +73,14 @@
"xmlbuilder2": "^3.1.1" "xmlbuilder2": "^3.1.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.26.0", "@eslint/js": "^9.27.0",
"eslint": "^9.26.0", "eslint": "^9.27.0",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"globals": "^15.15.0", "globals": "^15.15.0",
"mock-require": "^3.0.3", "mock-require": "^3.0.3",
"p-limit": "^3.1.0", "p-limit": "^3.1.0",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"supertest": "^7.1.1", "supertest": "^7.1.1",
"vitest": "^3.1.3" "vitest": "^3.1.4"
} }
} }

View File

@@ -6,7 +6,7 @@ require("dotenv").config({
function urlBuilder(realmId, object, query = null) { function urlBuilder(realmId, object, query = null) {
return `https://${ return `https://${
process.env.NODE_ENV === "production" ? "" : "sandbox-" process.env.NODE_ENV === "production" ? "" : "sandbox-"
}quickbooks.api.intuit.com/v3/company/${realmId}/${object}${query ? `?query=${encodeURIComponent(query)}` : ""}`; }quickbooks.api.intuit.com/v3/company/${realmId}/${object}?minorversion=75${query ? `&query=${encodeURIComponent(query)}` : ""}`;
} }
function StandardizeName(str) { function StandardizeName(str) {

View File

@@ -4,4 +4,5 @@ exports.chatter = require("./chatter").default;
exports.claimscorp = require("./claimscorp").default; exports.claimscorp = require("./claimscorp").default;
exports.kaizen = require("./kaizen").default; exports.kaizen = require("./kaizen").default;
exports.usageReport = require("./usageReport").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
View 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;

View File

@@ -185,7 +185,7 @@ async function uploadViaSFTP(csvObj) {
await sftp.connect(ftpSetup); await sftp.connect(ftpSetup);
try { 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, { logger.log("podium-sftp-upload", "DEBUG", "api", csvObj.bodyshopid, {
imexshopid: csvObj.imexshopid, imexshopid: csvObj.imexshopid,
filename: csvObj.filename, filename: csvObj.filename,

View File

@@ -1596,6 +1596,7 @@ query QUERY_JOB_COSTING_DETAILS($id: uuid!) {
ca_customer_gst ca_customer_gst
dms_allocation dms_allocation
cieca_pfl cieca_pfl
cieca_stl
materials materials
joblines(where: { removed: { _eq: false } }) { joblines(where: { removed: { _eq: false } }) {
id id
@@ -1712,6 +1713,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
ca_customer_gst ca_customer_gst
dms_allocation dms_allocation
cieca_pfl cieca_pfl
cieca_stl
materials materials
joblines(where: {removed: {_eq: false}}) { joblines(where: {removed: {_eq: false}}) {
id id

View File

@@ -567,6 +567,29 @@ function GenerateCostingData(job) {
); );
} }
if (InstanceManager({ imex: false, rome: true })) {
const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_type === "OTTW");
const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_type === "OTST");
if (!jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]])
jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = Dinero();
jobLineTotalsByProfitCenter.additional[defaultProfits["TOW"]] = stlTowing
? Dinero({ amount: Math.round(stlTowing.ttl_amt * 100) })
: Dinero({
amount: Math.round((job.towing_payable || 0) * 100)
});
if (!jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]])
jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = Dinero();
jobLineTotalsByProfitCenter.additional[defaultProfits["STO"]] = stlStorage
? Dinero({ amount: Math.round(stlStorage.ttl_amt * 100) })
: Dinero({
amount: Math.round((job.storage_payable || 0) * 100)
});
}
//Is it a DMS Setup? //Is it a DMS Setup?
const selectedDmsAllocationConfig = const selectedDmsAllocationConfig =
(job.bodyshop.md_responsibility_centers.dms_defaults && (job.bodyshop.md_responsibility_centers.dms_defaults &&

View File

@@ -138,6 +138,9 @@ router.post("/canvastest", validateFirebaseIdTokenMiddleware, canvastest);
// Alert Check // Alert Check
router.post("/alertcheck", eventAuthorizationMiddleware, alertCheck); router.post("/alertcheck", eventAuthorizationMiddleware, alertCheck);
//EMS Upload
router.post("/emsupload", validateFirebaseIdTokenMiddleware, data.emsUpload);
// Redis Cache Routes // Redis Cache Routes
router.post("/bodyshop-cache", eventAuthorizationMiddleware, updateBodyshopCache); router.post("/bodyshop-cache", eventAuthorizationMiddleware, updateBodyshopCache);

View File

@@ -7,7 +7,7 @@ const { status, markConversationRead } = require("../sms/status");
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
// Twilio Webhook Middleware for production // Twilio Webhook Middleware for production
// TODO: Look into this because it technically is never validating anything // TODO: This is never actually doing anything, we should probably verify
const twilioWebhookMiddleware = twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" }); const twilioWebhookMiddleware = twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" });
router.post("/receive", twilioWebhookMiddleware, receive); router.post("/receive", twilioWebhookMiddleware, receive);

View File

@@ -1,17 +1,23 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries"); const {
FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID,
UNARCHIVE_CONVERSATION,
CREATE_CONVERSATION,
INSERT_MESSAGE
} = require("../graphql-client/queries");
const { phone } = require("phone"); const { phone } = require("phone");
const { admin } = require("../firebase/firebase-handler"); const { admin } = require("../firebase/firebase-handler");
const logger = require("../utils/logger");
const InstanceManager = require("../utils/instanceMgr").default; const InstanceManager = require("../utils/instanceMgr").default;
exports.receive = async (req, res) => { /**
* Receive SMS messages from Twilio and process them
* @param req
* @param res
* @returns {Promise<*>}
*/
const receive = async (req, res) => {
const { const {
logger,
ioRedis, ioRedis,
ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }
} = req; } = req;
@@ -20,7 +26,7 @@ exports.receive = async (req, res) => {
msid: req.body.SmsMessageSid, msid: req.body.SmsMessageSid,
text: req.body.Body, text: req.body.Body,
image: !!req.body.MediaUrl0, image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body) image_path: generateMediaArray(req.body, logger)
}; };
logger.log("sms-inbound", "DEBUG", "api", null, loggerData); logger.log("sms-inbound", "DEBUG", "api", null, loggerData);
@@ -35,7 +41,7 @@ exports.receive = async (req, res) => {
try { try {
// Step 1: Find the bodyshop and existing conversation // Step 1: Find the bodyshop and existing conversation
const response = await client.request(queries.FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, { const response = await client.request(FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, {
mssid: req.body.MessagingServiceSid, mssid: req.body.MessagingServiceSid,
phone: phone(req.body.From).phoneNumber phone: phone(req.body.From).phoneNumber
}); });
@@ -46,7 +52,7 @@ exports.receive = async (req, res) => {
const bodyshop = response.bodyshops[0]; const bodyshop = response.bodyshops[0];
// Sort conversations by `updated_at` (or `created_at`) and pick the last one // Step 4: Process conversation
const sortedConversations = bodyshop.conversations.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); const sortedConversations = bodyshop.conversations.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
const existingConversation = sortedConversations.length const existingConversation = sortedConversations.length
? sortedConversations[sortedConversations.length - 1] ? sortedConversations[sortedConversations.length - 1]
@@ -57,25 +63,21 @@ exports.receive = async (req, res) => {
msid: req.body.SmsMessageSid, msid: req.body.SmsMessageSid,
text: req.body.Body, text: req.body.Body,
image: !!req.body.MediaUrl0, image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body), image_path: generateMediaArray(req.body, logger),
isoutbound: false, isoutbound: false,
userid: null // Add additional fields as necessary userid: null
}; };
if (existingConversation) { if (existingConversation) {
// Use the existing conversation
conversationid = existingConversation.id; conversationid = existingConversation.id;
// Unarchive the conversation if necessary
if (existingConversation.archived) { if (existingConversation.archived) {
await client.request(queries.UNARCHIVE_CONVERSATION, { await client.request(UNARCHIVE_CONVERSATION, {
id: conversationid, id: conversationid,
archived: false archived: false
}); });
} }
} else { } else {
// Create a new conversation const newConversationResponse = await client.request(CREATE_CONVERSATION, {
const newConversationResponse = await client.request(queries.CREATE_CONVERSATION, {
conversation: { conversation: {
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
phone_num: phone(req.body.From).phoneNumber, phone_num: phone(req.body.From).phoneNumber,
@@ -86,13 +88,12 @@ exports.receive = async (req, res) => {
conversationid = createdConversation.id; conversationid = createdConversation.id;
} }
// Ensure `conversationid` is added to the message
newMessage.conversationid = conversationid; newMessage.conversationid = conversationid;
// Step 3: Insert the message into the conversation // Step 5: Insert the message
const insertresp = await client.request(queries.INSERT_MESSAGE, { const insertresp = await client.request(INSERT_MESSAGE, {
msg: newMessage, msg: newMessage,
conversationid: conversationid conversationid
}); });
const message = insertresp?.insert_messages?.returning?.[0]; const message = insertresp?.insert_messages?.returning?.[0];
@@ -102,8 +103,7 @@ exports.receive = async (req, res) => {
throw new Error("Conversation data is missing from the response."); throw new Error("Conversation data is missing from the response.");
} }
// Step 4: Notify clients through Redis // Step 6: Notify clients
const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id);
const conversationRoom = getBodyshopConversationRoom({ const conversationRoom = getBodyshopConversationRoom({
bodyshopId: conversation.bodyshop.id, bodyshopId: conversation.bodyshop.id,
conversationId: conversation.id conversationId: conversation.id
@@ -116,6 +116,8 @@ exports.receive = async (req, res) => {
msid: message.sid msid: message.sid
}; };
const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id);
ioRedis.to(broadcastRoom).emit("new-message-summary", { ioRedis.to(broadcastRoom).emit("new-message-summary", {
...commonPayload, ...commonPayload,
existingConversation: !!existingConversation, existingConversation: !!existingConversation,
@@ -131,13 +133,13 @@ exports.receive = async (req, res) => {
summary: false summary: false
}); });
// Step 5: Send FCM notification // Step 7: Send FCM notification
const fcmresp = await admin.messaging().send({ const fcmresp = await admin.messaging().send({
topic: `${message.conversation.bodyshop.imexshopid}-messaging`, topic: `${message.conversation.bodyshop.imexshopid}-messaging`,
notification: { notification: {
title: InstanceManager({ title: InstanceManager({
imex: `ImEX Online Message - ${message.conversation.phone_num}`, imex: `ImEX Online Message - ${message.conversation.phone_num}`,
rome: `Rome Online Message - ${message.conversation.phone_num}`, rome: `Rome Online Message - ${message.conversation.phone_num}`
}), }),
body: message.image_path ? `Image ${message.text}` : message.text body: message.image_path ? `Image ${message.text}` : message.text
}, },
@@ -157,11 +159,17 @@ exports.receive = async (req, res) => {
res.status(200).send(""); res.status(200).send("");
} catch (e) { } catch (e) {
handleError(req, e, res, "RECEIVE_MESSAGE"); handleError(req, e, res, "RECEIVE_MESSAGE", logger);
} }
}; };
const generateMediaArray = (body) => { /**
* Generate media array from the request body
* @param body
* @param logger
* @returns {null|*[]}
*/
const generateMediaArray = (body, logger) => {
const { NumMedia } = body; const { NumMedia } = body;
if (parseInt(NumMedia) > 0) { if (parseInt(NumMedia) > 0) {
const ret = []; const ret = [];
@@ -174,12 +182,20 @@ const generateMediaArray = (body) => {
} }
}; };
const handleError = (req, error, res, context) => { /**
* Handle error logging and response
* @param req
* @param error
* @param res
* @param context
* @param logger
*/
const handleError = (req, error, res, context, logger) => {
logger.log("sms-inbound-error", "ERROR", "api", null, { logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid, msid: req.body.SmsMessageSid,
text: req.body.Body, text: req.body.Body,
image: !!req.body.MediaUrl0, image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body), image_path: generateMediaArray(req.body, logger),
messagingServiceSid: req.body.MessagingServiceSid, messagingServiceSid: req.body.MessagingServiceSid,
context, context,
error error
@@ -187,3 +203,7 @@ const handleError = (req, error, res, context) => {
res.status(500).json({ error: error.message || "Internal Server Error" }); res.status(500).json({ error: error.message || "Internal Server Error" });
}; };
module.exports = {
receive
};

View File

@@ -1,19 +1,20 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const twilio = require("twilio"); const twilio = require("twilio");
const { phone } = require("phone"); const { phone } = require("phone");
const queries = require("../graphql-client/queries"); const { INSERT_MESSAGE } = require("../graphql-client/queries");
const logger = require("../utils/logger");
const client = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY); const client = twilio(process.env.TWILIO_AUTH_TOKEN, process.env.TWILIO_AUTH_KEY);
const gqlClient = require("../graphql-client/graphql-client").client; const gqlClient = require("../graphql-client/graphql-client").client;
exports.send = async (req, res) => { /**
* Send an outbound SMS message
* @param req
* @param res
* @returns {Promise<void>}
*/
const send = async (req, res) => {
const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid } = req.body; const { to, messagingServiceSid, body, conversationid, selectedMedia, imexshopid } = req.body;
const { const {
ioRedis, ioRedis,
logger,
ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }
} = req; } = req;
@@ -25,8 +26,8 @@ exports.send = async (req, res) => {
conversationid, conversationid,
isoutbound: true, isoutbound: true,
userid: req.user.email, userid: req.user.email,
image: req.body.selectedMedia.length > 0, image: selectedMedia.length > 0,
image_path: req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] image_path: selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : []
}); });
if (!to || !messagingServiceSid || (!body && selectedMedia.length === 0) || !conversationid) { if (!to || !messagingServiceSid || (!body && selectedMedia.length === 0) || !conversationid) {
@@ -38,8 +39,8 @@ exports.send = async (req, res) => {
conversationid, conversationid,
isoutbound: true, isoutbound: true,
userid: req.user.email, userid: req.user.email,
image: req.body.selectedMedia.length > 0, image: selectedMedia.length > 0,
image_path: req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] image_path: selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : []
}); });
res.status(400).json({ success: false, message: "Missing required parameter(s)." }); res.status(400).json({ success: false, message: "Missing required parameter(s)." });
return; return;
@@ -59,12 +60,15 @@ exports.send = async (req, res) => {
conversationid, conversationid,
isoutbound: true, isoutbound: true,
userid: req.user.email, userid: req.user.email,
image: req.body.selectedMedia.length > 0, image: selectedMedia.length > 0,
image_path: req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [] image_path: selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : []
}; };
try { try {
const gqlResponse = await gqlClient.request(queries.INSERT_MESSAGE, { msg: newMessage, conversationid }); const gqlResponse = await gqlClient.request(INSERT_MESSAGE, {
msg: newMessage,
conversationid
});
logger.log("sms-outbound-success", "DEBUG", req.user.email, null, { logger.log("sms-outbound-success", "DEBUG", req.user.email, null, {
msid: message.sid, msid: message.sid,
@@ -111,3 +115,7 @@ exports.send = async (req, res) => {
res.status(500).json({ success: false, message: "Failed to send message through Twilio." }); res.status(500).json({ success: false, message: "Failed to send message through Twilio." });
} }
}; };
module.exports = {
send
};

View File

@@ -1,13 +1,14 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const client = require("../graphql-client/graphql-client").client; const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries"); const { UPDATE_MESSAGE_STATUS, MARK_MESSAGES_AS_READ } = require("../graphql-client/queries");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
exports.status = async (req, res) => { /**
* Handle the status of an SMS message
* @param req
* @param res
* @returns {Promise<*>}
*/
const status = async (req, res) => {
const { SmsSid, SmsStatus } = req.body; const { SmsSid, SmsStatus } = req.body;
const { const {
ioRedis, ioRedis,
@@ -21,7 +22,7 @@ exports.status = async (req, res) => {
} }
// Update message status in the database // Update message status in the database
const response = await client.request(queries.UPDATE_MESSAGE_STATUS, { const response = await client.request(UPDATE_MESSAGE_STATUS, {
msid: SmsSid, msid: SmsSid,
fields: { status: SmsStatus } fields: { status: SmsStatus }
}); });
@@ -65,7 +66,13 @@ exports.status = async (req, res) => {
} }
}; };
exports.markConversationRead = async (req, res) => { /**
* Mark a conversation as read
* @param req
* @param res
* @returns {Promise<*>}
*/
const markConversationRead = async (req, res) => {
const { const {
ioRedis, ioRedis,
ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom } ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }
@@ -80,7 +87,7 @@ exports.markConversationRead = async (req, res) => {
} }
try { try {
const response = await client.request(queries.MARK_MESSAGES_AS_READ, { const response = await client.request(MARK_MESSAGES_AS_READ, {
conversationId conversationId
}); });
@@ -104,3 +111,8 @@ exports.markConversationRead = async (req, res) => {
res.status(500).json({ error: "Failed to mark conversation as read." }); res.status(500).json({ error: "Failed to mark conversation as read." });
} }
}; };
module.exports = {
status,
markConversationRead
};

View File

@@ -1,3 +1,11 @@
/**
* @module ioHelpers
* @param app
* @param api
* @param io
* @param logger
* @returns {{getBodyshopRoom: (function(*): string), getBodyshopConversationRoom: (function({bodyshopId: *, conversationId: *}): string)}}
*/
const applyIOHelpers = ({ app, api, io, logger }) => { const applyIOHelpers = ({ app, api, io, logger }) => {
// Global Bodyshop Room // Global Bodyshop Room
const getBodyshopRoom = (bodyshopId) => `bodyshop-broadcast-room:${bodyshopId}`; const getBodyshopRoom = (bodyshopId) => `bodyshop-broadcast-room:${bodyshopId}`;

View File

@@ -9,6 +9,7 @@ const {
const { defaultProvider } = require("@aws-sdk/credential-provider-node"); const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const { InstanceRegion } = require("./instanceMgr"); const { InstanceRegion } = require("./instanceMgr");
const { isString, isEmpty } = require("lodash"); const { isString, isEmpty } = require("lodash");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const createS3Client = () => { const createS3Client = () => {
const S3Options = { const S3Options = {
@@ -95,6 +96,17 @@ const createS3Client = () => {
throw error; 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 { return {
uploadFileToS3, uploadFileToS3,
downloadFileFromS3, downloadFileFromS3,
@@ -102,8 +114,12 @@ const createS3Client = () => {
deleteFileFromS3, deleteFileFromS3,
copyFileInS3, copyFileInS3,
fileExistsInS3, fileExistsInS3,
getPresignedUrl,
...s3Client ...s3Client
}; };
}; };
module.exports = createS3Client(); module.exports = createS3Client();