Compare commits
1 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d52426f5f5 |
@@ -12791,6 +12791,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>allow_text_message</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>checklist</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -42593,6 +42614,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>allow_text_message</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>name</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
220
client/package-lock.json
generated
220
client/package-lock.json
generated
@@ -15,17 +15,17 @@
|
||||
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
||||
"@firebase/analytics": "^0.10.16",
|
||||
"@firebase/app": "^0.13.0",
|
||||
"@firebase/auth": "^1.10.6",
|
||||
"@firebase/firestore": "^4.7.16",
|
||||
"@firebase/auth": "^1.10.5",
|
||||
"@firebase/firestore": "^4.7.15",
|
||||
"@firebase/messaging": "^0.12.21",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@sentry/cli": "^2.46.0",
|
||||
"@sentry/react": "^9.23.0",
|
||||
"@sentry/cli": "^2.45.0",
|
||||
"@sentry/react": "^9.22.0",
|
||||
"@sentry/vite-plugin": "^3.5.0",
|
||||
"@splitsoftware/splitio-react": "^2.1.1",
|
||||
"@tanem/react-nprogress": "^5.0.53",
|
||||
"antd": "^5.25.3",
|
||||
"antd": "^5.25.2",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^4.3.0",
|
||||
"autosize": "^6.0.1",
|
||||
@@ -48,7 +48,6 @@
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-url": "^8.0.1",
|
||||
"object-hash": "^3.0.0",
|
||||
"phone": "^3.1.59",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^9.2.0",
|
||||
"raf-schd": "^4.0.3",
|
||||
@@ -60,7 +59,7 @@
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-grid-gallery": "^1.0.1",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-i18next": "^15.5.2",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -100,7 +99,7 @@
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@vitejs/plugin-react": "^4.5.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"browserslist": "^4.24.5",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"chalk": "^5.4.1",
|
||||
@@ -2967,9 +2966,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/auth": {
|
||||
"version": "1.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.6.tgz",
|
||||
"integrity": "sha512-cFbo2FymQltog4atI9cKTO6CxKxS0dOMXslTQrlNZRH7qhDG44/d7QeI6GXLweFZtrnlecf52ESnNz1DU6ek8w==",
|
||||
"version": "1.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.5.tgz",
|
||||
"integrity": "sha512-6wF/NdMTwObL4RNQePunuzMr9O3gyftisvFZFFKf57D2HONXo87YymogRV8d+Z7SLA0rcNBN1gLJVk2D0y97gA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.6.17",
|
||||
@@ -3004,9 +3003,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/firestore": {
|
||||
"version": "4.7.16",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.16.tgz",
|
||||
"integrity": "sha512-5OpvlwYVUTLEnqewOlXmtIpH8t2ISlZHDW0NDbKROM2D0ATMqFkMHdvl+/wz9zOAcb8GMQYlhCihOnVAliUbpQ==",
|
||||
"version": "4.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.15.tgz",
|
||||
"integrity": "sha512-FgWTmkNBEXdKCoN2ngBNjrMaXuBx6QwjiZZVnOGg+VjUmiBq5gAqlDIW5bZY6i/NYvLUrWugdqIs7y9GHEqwww==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.6.17",
|
||||
@@ -3886,13 +3885,6 @@
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz",
|
||||
"integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/plugin-babel": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
||||
@@ -4469,50 +4461,50 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@sentry-internal/browser-utils": {
|
||||
"version": "9.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.23.0.tgz",
|
||||
"integrity": "sha512-hyN2Q6mh7ggw8sDVHeRyWz5LR6gjvf8zHSzQnMaF7QkeSyaeGM/SVSL4ODwqR9TRH7U2ku6nZFMbKhaBPV+Hfg==",
|
||||
"version": "9.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-9.22.0.tgz",
|
||||
"integrity": "sha512-Ou1tBnVxFAIn8i9gvrWzRotNJQYiu3awNXpsFCw6qFwmiKAVPa6b13vCdolhXnrIiuR77jY1LQnKh9hXpoRzsg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "9.23.0"
|
||||
"@sentry/core": "9.22.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/feedback": {
|
||||
"version": "9.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.23.0.tgz",
|
||||
"integrity": "sha512-Xf+KqV69TBiPo1gk2EsU6O/dumuTMxWOF52uVWJddQYI3pQTU5DqSeoZ5AY76bIIhV9n6AEFDGqNPXmuj4Acrw==",
|
||||
"version": "9.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-9.22.0.tgz",
|
||||
"integrity": "sha512-zgMVkoC61fgi41zLcSZA59vOtKxcLrKBo1ECYhPD1hxEaneNqY5fhXDwlQBw96P5l2yqkgfX6YZtSdU4ejI9yA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/core": "9.23.0"
|
||||
"@sentry/core": "9.22.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay": {
|
||||
"version": "9.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.23.0.tgz",
|
||||
"integrity": "sha512-0/q15tvSboaK7/05BFQhs71bqgHKejJoDJgXmH0lySqgsRh/S18867ZxQNiuYhuVt337h07u1QaCyjnNJKHfuA==",
|
||||
"version": "9.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-9.22.0.tgz",
|
||||
"integrity": "sha512-9GOycoKbrclcRXfcbNV8svbmAsOS5R4wXBQmKF4pFLkmFA/lJv9kdZSNYkRvkrxdNfbMIJXP+DV9EqTZcryXig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "9.23.0",
|
||||
"@sentry/core": "9.23.0"
|
||||
"@sentry-internal/browser-utils": "9.22.0",
|
||||
"@sentry/core": "9.22.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry-internal/replay-canvas": {
|
||||
"version": "9.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.23.0.tgz",
|
||||
"integrity": "sha512-cYlw5svJjyPequm0PJjFGLpee86L1NieONEHlujOXkIG6IEriiorMm+8bNpGsHRuyvg41B+4P/YmcQAGtEGxXg==",
|
||||
"version": "9.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-9.22.0.tgz",
|
||||
"integrity": "sha512-EcG9IMSEalFe49kowBTJObWjof/iHteDwpyuAszsFDdQUYATrVUtwpwN7o52vDYWJud4arhjrQnMamIGxa79eQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/replay": "9.23.0",
|
||||
"@sentry/core": "9.23.0"
|
||||
"@sentry-internal/replay": "9.22.0",
|
||||
"@sentry/core": "9.22.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -4528,16 +4520,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/browser": {
|
||||
"version": "9.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.23.0.tgz",
|
||||
"integrity": "sha512-QRkNxWys8e088260vByztoTsEVZ0W6v/XnZfKT6wkPPGn0aFeOrg/xjgxfI8D5huqZCxT28Cf23akOOly4FXjg==",
|
||||
"version": "9.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-9.22.0.tgz",
|
||||
"integrity": "sha512-3TeRm74dvX0JdjX0AgkQa+22iUHwHnY+Q6M05NZ+tDeCNHGK/mEBTeqquS1oQX67jWyuvYmG3VV6RJUxtG9Paw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry-internal/browser-utils": "9.23.0",
|
||||
"@sentry-internal/feedback": "9.23.0",
|
||||
"@sentry-internal/replay": "9.23.0",
|
||||
"@sentry-internal/replay-canvas": "9.23.0",
|
||||
"@sentry/core": "9.23.0"
|
||||
"@sentry-internal/browser-utils": "9.22.0",
|
||||
"@sentry-internal/feedback": "9.22.0",
|
||||
"@sentry-internal/replay": "9.22.0",
|
||||
"@sentry-internal/replay-canvas": "9.22.0",
|
||||
"@sentry/core": "9.22.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -4728,9 +4720,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli": {
|
||||
"version": "2.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.46.0.tgz",
|
||||
"integrity": "sha512-nqoPl7UCr446QFkylrsRrUXF51x8Z9dGquyf4jaQU+OzbOJMqclnYEvU6iwbwvaw3tu/2DnoZE/Og+Nq1h63sA==",
|
||||
"version": "2.45.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.45.0.tgz",
|
||||
"integrity": "sha512-4sWu7zgzgHAjIxIjXUA/66qgeEf5ZOlloO+/JaGD5qXNSW0G7KMTR6iYjReNKMgdBCTH6bUUt9qiuA+Ex9Masw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
@@ -4747,20 +4739,20 @@
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@sentry/cli-darwin": "2.46.0",
|
||||
"@sentry/cli-linux-arm": "2.46.0",
|
||||
"@sentry/cli-linux-arm64": "2.46.0",
|
||||
"@sentry/cli-linux-i686": "2.46.0",
|
||||
"@sentry/cli-linux-x64": "2.46.0",
|
||||
"@sentry/cli-win32-arm64": "2.46.0",
|
||||
"@sentry/cli-win32-i686": "2.46.0",
|
||||
"@sentry/cli-win32-x64": "2.46.0"
|
||||
"@sentry/cli-darwin": "2.45.0",
|
||||
"@sentry/cli-linux-arm": "2.45.0",
|
||||
"@sentry/cli-linux-arm64": "2.45.0",
|
||||
"@sentry/cli-linux-i686": "2.45.0",
|
||||
"@sentry/cli-linux-x64": "2.45.0",
|
||||
"@sentry/cli-win32-arm64": "2.45.0",
|
||||
"@sentry/cli-win32-i686": "2.45.0",
|
||||
"@sentry/cli-win32-x64": "2.45.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-darwin": {
|
||||
"version": "2.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.46.0.tgz",
|
||||
"integrity": "sha512-5Ll+e5KAdIk9OYiZO8aifMBRNWmNyPjSqdjaHlBC1Qfh7pE3b1zyzoHlsUazG0bv0sNrSGea8e7kF5wIO1hvyg==",
|
||||
"version": "2.45.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.45.0.tgz",
|
||||
"integrity": "sha512-p4Uxfv/L2fQdP3/wYnKVVz9gzZJf/1Xp9D+6raax/3Bu5y87yHYUqcdt98y/VAXQD4ofp2QgmhGUVPofvQNZmg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -4771,9 +4763,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-linux-arm": {
|
||||
"version": "2.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.46.0.tgz",
|
||||
"integrity": "sha512-WRrLNq/TEX/TNJkGqq6Ad0tGyapd5dwlxtsPbVBrIdryuL1mA7VCBoaHBr3kcwJLsgBHFH0lmkMee2ubNZZdkg==",
|
||||
"version": "2.45.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.45.0.tgz",
|
||||
"integrity": "sha512-6sEskFLlFKJ+e0MOYgIclBTUX5jYMyYhHIxXahEkI/4vx6JO0uvpyRAkUJRpJkRh/lPog0FM+tbP3so+VxB2qQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -4781,17 +4773,16 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux",
|
||||
"freebsd",
|
||||
"android"
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-linux-arm64": {
|
||||
"version": "2.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.46.0.tgz",
|
||||
"integrity": "sha512-OEJN8yAjI9y5B4telyqzu27Hi3+S4T8VxZCqJz1+z2Mp0Q/MZ622AahVPpcrVq/5bxrnlZR16+lKh8L1QwNFPg==",
|
||||
"version": "2.45.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.45.0.tgz",
|
||||
"integrity": "sha512-gUcLoEjzg7AIc4QQGEZwRHri+EHf3Gcms9zAR1VHiNF3/C/jL4WeDPJF2YiWAQt6EtH84tHiyhw1Ab/R8XFClg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4799,17 +4790,16 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux",
|
||||
"freebsd",
|
||||
"android"
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-linux-i686": {
|
||||
"version": "2.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.46.0.tgz",
|
||||
"integrity": "sha512-xko3/BVa4LX8EmRxVOCipV+PwfcK5Xs8lP6lgF+7NeuAHMNL4DqF6iV9rrN8gkGUHCUI9RXSve37uuZnFy55+Q==",
|
||||
"version": "2.45.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.45.0.tgz",
|
||||
"integrity": "sha512-VmmOaEAzSW23YdGNdy/+oQjCNAMY+HmOGA77A25/ep/9AV7PQB6FI7xO5Y1PVvlkxZFJ23e373njSsEeg4uDZw==",
|
||||
"cpu": [
|
||||
"x86",
|
||||
"ia32"
|
||||
@@ -4818,17 +4808,16 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux",
|
||||
"freebsd",
|
||||
"android"
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-linux-x64": {
|
||||
"version": "2.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.46.0.tgz",
|
||||
"integrity": "sha512-hJ1g5UEboYcOuRia96LxjJ0jhnmk8EWLDvlGnXLnYHkwy3ree/L7sNgdp/QsY8Z4j2PGO5f22Va+UDhSjhzlfQ==",
|
||||
"version": "2.45.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.45.0.tgz",
|
||||
"integrity": "sha512-a0Oj68mrb25a0WjX/ShZ6AAd4PPiuLcgyzQr7bl2+DvYxIOajwkGbR+CZFEhOVZcfhTnixKy/qIXEzApEPHPQg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4836,17 +4825,16 @@
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux",
|
||||
"freebsd",
|
||||
"android"
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-win32-arm64": {
|
||||
"version": "2.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.46.0.tgz",
|
||||
"integrity": "sha512-mN7cpPoCv2VExFRGHt+IoK11yx4pM4ADZQGEso5BAUZ5duViXB2WrAXCLd8DrwMnP0OE978a7N8OtzsFqjkbNA==",
|
||||
"version": "2.45.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.45.0.tgz",
|
||||
"integrity": "sha512-vn+CwS4p+52pQSLNPoi20ZOrQmv01ZgAmuMnjkh1oUZfTyBAwWLrAh6Cy4cztcN8DfL5dOWKQBo8DBKURE4ttg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4860,9 +4848,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-win32-i686": {
|
||||
"version": "2.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.46.0.tgz",
|
||||
"integrity": "sha512-6F73AUE3lm71BISUO19OmlnkFD5WVe4/wA1YivtLZTc1RU3eUYJLYxhDfaH3P77+ycDppQ2yCgemLRaA4A8mNQ==",
|
||||
"version": "2.45.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.45.0.tgz",
|
||||
"integrity": "sha512-8mMoDdlwxtcdNIMtteMK7dbi7054jak8wKSHJ5yzMw8UmWxC5thc/gXBc1uPduiaI56VjoJV+phWHBKCD+6I4w==",
|
||||
"cpu": [
|
||||
"x86",
|
||||
"ia32"
|
||||
@@ -4877,9 +4865,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/cli-win32-x64": {
|
||||
"version": "2.46.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.46.0.tgz",
|
||||
"integrity": "sha512-yuGVcfepnNL84LGA0GjHzdMIcOzMe0bjPhq/rwPsPN+zu11N+nPR2wV2Bum4U0eQdqYH3iAlMdL5/BEQfuLJww==",
|
||||
"version": "2.45.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.45.0.tgz",
|
||||
"integrity": "sha512-ZvK9cIqFaq7vZ0jkHJ/xh5au6902Dr+AUxSk6L6vCL7JCe2p93KGL/4d8VFB5PD/P7Y9b+105G/e0QIFKzpeOw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4914,22 +4902,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/core": {
|
||||
"version": "9.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.23.0.tgz",
|
||||
"integrity": "sha512-9846pn/BvASGgl7WsnKY4xry98WreP9ToeLfCQTQOf+pNfD/qNPn1/0xPInGni3LVMAXRtfHHMPm2Ghz255N7A==",
|
||||
"version": "9.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.22.0.tgz",
|
||||
"integrity": "sha512-ixvtKmPF42Y6ckGUbFlB54OWI75H2gO5UYHojO6eXFpS7xO3ZGgV/QH6wb40mWK+0w5XZ0233FuU9VpsuE6mKA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@sentry/react": {
|
||||
"version": "9.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.23.0.tgz",
|
||||
"integrity": "sha512-2J/oOx8jd7Jr2koYIe5IcJyStHBXpjkQnxawo54Zyyvzc96MftyM2Dv5TeYdz7fChU1NIXw7BVbEpkQ9XEQlqg==",
|
||||
"version": "9.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-9.22.0.tgz",
|
||||
"integrity": "sha512-mI43NnioBYdG5TiXqRlhV1feZs9bnrrl+k5HOHBK7VQtymaXO0fkcsRLZTkdSgLRLMJGasZuvVhq2xK+18QyWQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sentry/browser": "9.23.0",
|
||||
"@sentry/core": "9.23.0",
|
||||
"@sentry/browser": "9.22.0",
|
||||
"@sentry/core": "9.22.0",
|
||||
"hoist-non-react-statics": "^3.3.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -5805,16 +5793,15 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz",
|
||||
"integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==",
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz",
|
||||
"integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.26.10",
|
||||
"@babel/plugin-transform-react-jsx-self": "^7.25.9",
|
||||
"@babel/plugin-transform-react-jsx-source": "^7.25.9",
|
||||
"@rolldown/pluginutils": "1.0.0-beta.9",
|
||||
"@types/babel__core": "^7.20.5",
|
||||
"react-refresh": "^0.17.0"
|
||||
},
|
||||
@@ -6104,12 +6091,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/antd": {
|
||||
"version": "5.25.3",
|
||||
"resolved": "https://registry.npmjs.org/antd/-/antd-5.25.3.tgz",
|
||||
"integrity": "sha512-tBBcAFRjmWM3sitxrL/FEbQL+MTQntYY5bGa5c1ZZZHXWCynkhS3Ch/gy25mGMUY1M/9Uw3pH029v/RGht1x3w==",
|
||||
"version": "5.25.2",
|
||||
"resolved": "https://registry.npmjs.org/antd/-/antd-5.25.2.tgz",
|
||||
"integrity": "sha512-7R2nUvlHhey7Trx64+hCtGXOiy+DTUs1Lv5bwbV1LzEIZIhWb0at1AM6V3K108a5lyoR9n7DX3ptlLF7uYV/DQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^7.2.1",
|
||||
"@ant-design/colors": "^7.2.0",
|
||||
"@ant-design/cssinjs": "^1.23.0",
|
||||
"@ant-design/cssinjs-utils": "^1.1.3",
|
||||
"@ant-design/fast-color": "^2.0.6",
|
||||
@@ -6169,9 +6156,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/antd/node_modules/@ant-design/colors": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz",
|
||||
"integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.0.tgz",
|
||||
"integrity": "sha512-bjTObSnZ9C/O8MB/B4OUtd/q9COomuJAR2SYfhxLyHvCKn4EKwCN3e+fWGMo7H5InAyV0wL17jdE9ALrdOW/6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ant-design/fast-color": "^2.0.6"
|
||||
@@ -13279,15 +13266,6 @@
|
||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/phone": {
|
||||
"version": "3.1.59",
|
||||
"resolved": "https://registry.npmjs.org/phone/-/phone-3.1.59.tgz",
|
||||
"integrity": "sha512-CUv22jw0Zgrb/h7v3sEd262zJXS/66h7zyCCRIynx+2FswAJuuFsXsJkIxMUT4UcosKxDx1bJwdZeGnDELLsCw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -14425,9 +14403,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz",
|
||||
"integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==",
|
||||
"version": "15.5.1",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.1.tgz",
|
||||
"integrity": "sha512-C8RZ7N7H0L+flitiX6ASjq9p5puVJU1Z8VyL3OgM/QOMRf40BMZX+5TkpxzZVcTmOLPX5zlti4InEX5pFyiVeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.0",
|
||||
|
||||
@@ -14,17 +14,17 @@
|
||||
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
||||
"@firebase/analytics": "^0.10.16",
|
||||
"@firebase/app": "^0.13.0",
|
||||
"@firebase/auth": "^1.10.6",
|
||||
"@firebase/firestore": "^4.7.16",
|
||||
"@firebase/auth": "^1.10.5",
|
||||
"@firebase/firestore": "^4.7.15",
|
||||
"@firebase/messaging": "^0.12.21",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@sentry/cli": "^2.46.0",
|
||||
"@sentry/react": "^9.23.0",
|
||||
"@sentry/cli": "^2.45.0",
|
||||
"@sentry/react": "^9.22.0",
|
||||
"@sentry/vite-plugin": "^3.5.0",
|
||||
"@splitsoftware/splitio-react": "^2.1.1",
|
||||
"@tanem/react-nprogress": "^5.0.53",
|
||||
"antd": "^5.25.3",
|
||||
"antd": "^5.25.2",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^4.3.0",
|
||||
"autosize": "^6.0.1",
|
||||
@@ -47,7 +47,6 @@
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-url": "^8.0.1",
|
||||
"object-hash": "^3.0.0",
|
||||
"phone": "^3.1.59",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^9.2.0",
|
||||
"raf-schd": "^4.0.3",
|
||||
@@ -59,7 +58,7 @@
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-grid-gallery": "^1.0.1",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-i18next": "^15.5.2",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -140,7 +139,7 @@
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@vitejs/plugin-react": "^4.5.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"browserslist": "^4.24.5",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"chalk": "^5.4.1",
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Button, Form, InputNumber, Popover, Space } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
|
||||
export default function CABCpvrtCalculator({ disabled, form }) {
|
||||
const [visibility, setVisibility] = useState(false);
|
||||
|
||||
@@ -40,7 +39,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover destroyOnHidden content={popContent} open={visibility} disabled={disabled}>
|
||||
<Popover destroyTooltipOnHide content={popContent} open={visibility} disabled={disabled}>
|
||||
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||
<CalculatorFilled />
|
||||
</Button>
|
||||
|
||||
@@ -202,6 +202,8 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
text: message.text
|
||||
};
|
||||
|
||||
// Add cases for other known message types as needed
|
||||
|
||||
default:
|
||||
// Log a warning for unhandled message types
|
||||
logLocal("handleMessageChanged - Unhandled message type", { type: message.type });
|
||||
@@ -209,7 +211,7 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
}
|
||||
}
|
||||
|
||||
return messageRef;
|
||||
return messageRef; // Keep other messages unchanged
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -243,8 +245,11 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
});
|
||||
|
||||
const updatedList = existingList?.conversations
|
||||
? [newConversation, ...existingList.conversations.filter((conv) => conv.id !== newConversation.id)]
|
||||
: [newConversation]; // Prevent duplicates
|
||||
? [
|
||||
newConversation,
|
||||
...existingList.conversations.filter((conv) => conv.id !== newConversation.id) // Prevent duplicates
|
||||
]
|
||||
: [newConversation];
|
||||
|
||||
client.cache.writeQuery({
|
||||
query: CONVERSATION_LIST_QUERY,
|
||||
@@ -398,7 +403,6 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
logLocal("handleConversationChanged - Unhandled type", { type });
|
||||
client.cache.modify({
|
||||
@@ -415,95 +419,10 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Existing handler for phone number opt-out
|
||||
const handlePhoneNumberOptedOut = async (data) => {
|
||||
const { bodyshopid, phone_number } = data;
|
||||
logLocal("handlePhoneNumberOptedOut - Start", data);
|
||||
|
||||
try {
|
||||
client.cache.modify({
|
||||
id: "ROOT_QUERY",
|
||||
fields: {
|
||||
phone_number_opt_out(existing = [], { readField }) {
|
||||
const phoneNumberExists = existing.some(
|
||||
(ref) => readField("phone_number", ref) === phone_number && readField("bodyshopid", ref) === bodyshopid
|
||||
);
|
||||
|
||||
if (phoneNumberExists) {
|
||||
logLocal("handlePhoneNumberOptedOut - Phone number already in cache", { phone_number, bodyshopid });
|
||||
return existing;
|
||||
}
|
||||
|
||||
const newOptOut = {
|
||||
__typename: "phone_number_opt_out",
|
||||
id: `temporary-${phone_number}-${Date.now()}`,
|
||||
bodyshopid,
|
||||
phone_number,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
return [...existing, newOptOut];
|
||||
}
|
||||
},
|
||||
broadcast: true
|
||||
});
|
||||
|
||||
client.cache.evict({
|
||||
id: "ROOT_QUERY",
|
||||
fieldName: "phone_number_opt_out",
|
||||
args: { bodyshopid, search: phone_number }
|
||||
});
|
||||
client.cache.gc();
|
||||
|
||||
logLocal("handlePhoneNumberOptedOut - Cache updated successfully", data);
|
||||
} catch (error) {
|
||||
console.error("Error updating cache for phone number opt-out:", error);
|
||||
logLocal("handlePhoneNumberOptedOut - Error", { error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
// New handler for phone number opt-in
|
||||
const handlePhoneNumberOptedIn = async (data) => {
|
||||
const { bodyshopid, phone_number } = data;
|
||||
logLocal("handlePhoneNumberOptedIn - Start", data);
|
||||
|
||||
try {
|
||||
// Update the Apollo cache for GET_PHONE_NUMBER_OPT_OUTS by removing the phone number
|
||||
client.cache.modify({
|
||||
id: "ROOT_QUERY",
|
||||
fields: {
|
||||
phone_number_opt_out(existing = [], { readField }) {
|
||||
// Filter out the phone number from the opt-out list
|
||||
return existing.filter(
|
||||
(ref) => !(readField("phone_number", ref) === phone_number && readField("bodyshopid", ref) === bodyshopid)
|
||||
);
|
||||
}
|
||||
},
|
||||
broadcast: true // Trigger UI updates
|
||||
});
|
||||
|
||||
// Evict the cache entry to force a refetch on next query
|
||||
client.cache.evict({
|
||||
id: "ROOT_QUERY",
|
||||
fieldName: "phone_number_opt_out",
|
||||
args: { bodyshopid, search: phone_number }
|
||||
});
|
||||
client.cache.gc();
|
||||
|
||||
logLocal("handlePhoneNumberOptedIn - Cache updated successfully", data);
|
||||
} catch (error) {
|
||||
console.error("Error updating cache for phone number opt-in:", error);
|
||||
logLocal("handlePhoneNumberOptedIn - Error", { error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
socket.on("new-message-summary", handleNewMessageSummary);
|
||||
socket.on("new-message-detailed", handleNewMessageDetailed);
|
||||
socket.on("message-changed", handleMessageChanged);
|
||||
socket.on("conversation-changed", handleConversationChanged);
|
||||
socket.on("phone-number-opted-out", handlePhoneNumberOptedOut);
|
||||
socket.on("phone-number-opted-in", handlePhoneNumberOptedIn);
|
||||
};
|
||||
|
||||
export const unregisterMessagingHandlers = ({ socket }) => {
|
||||
@@ -512,6 +431,4 @@ export const unregisterMessagingHandlers = ({ socket }) => {
|
||||
socket.off("new-message-detailed");
|
||||
socket.off("message-changed");
|
||||
socket.off("conversation-changed");
|
||||
socket.off("phone-number-opted-out");
|
||||
socket.off("phone-number-opted-in");
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Badge, Card, List, Space, Tag, Tooltip } from "antd";
|
||||
import { Badge, Card, List, Space, Tag } from "antd";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
@@ -9,7 +9,6 @@ import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import _ from "lodash";
|
||||
import { ExclamationCircleOutlined } from "@ant-design/icons";
|
||||
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";
|
||||
@@ -89,13 +88,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
const cardExtra = (
|
||||
<>
|
||||
<Badge count={item.messages_aggregate.aggregate.count} />
|
||||
{hasOptOutEntry && (
|
||||
<Tooltip title={t("consent.text_body")}>
|
||||
<Tag color="red" icon={<ExclamationCircleOutlined />}>
|
||||
{t("messaging.labels.no_consent")}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
)}
|
||||
{hasOptOutEntry && <Tag color="red">{t("messaging.labels.no_consent")}</Tag>}
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
@@ -58,7 +58,6 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
userid
|
||||
created_at
|
||||
read
|
||||
is_system
|
||||
}
|
||||
`,
|
||||
data: message
|
||||
|
||||
@@ -13,14 +13,13 @@ import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-document
|
||||
import JobsDocumentImgproxyGalleryExternal from "../jobs-documents-imgproxy-gallery/jobs-documents-imgproxy-gallery.external.component";
|
||||
import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import "./chat-media-selector.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector);
|
||||
|
||||
export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, conversation }) {
|
||||
@@ -38,8 +37,9 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
jobId: 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
|
||||
});
|
||||
|
||||
@@ -56,11 +56,11 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
|
||||
//If Imageproxy is on, rely only on the LMS selector
|
||||
//If not on, use the old methods.
|
||||
const content = (
|
||||
<div className="media-selector-content">
|
||||
<div>
|
||||
{loading && <LoadingSpinner />}
|
||||
{error && <AlertComponent message={error.message} type="error" />}
|
||||
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
|
||||
<div className="error-message">{t("messaging.labels.maxtenimages")}</div>
|
||||
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
|
||||
) : null}
|
||||
|
||||
{Imgproxy.treatment === "on" ? (
|
||||
@@ -74,7 +74,7 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
|
||||
{bodyshop.uselocalmediaserver && open && (
|
||||
<JobDocumentsLocalGalleryExternal
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
jobId={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 && (
|
||||
<JobDocumentsLocalGalleryExternal
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
jobId={conversation.job_conversations[0]?.jobid}
|
||||
jobId={conversation.job_conversations[0] && conversation.job_conversations[0]?.jobid}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -100,17 +100,12 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
conversation.job_conversations.length === 0 ? (
|
||||
<div className="no-jobs-message">{t("messaging.errors.noattachedjobs")}</div>
|
||||
) : (
|
||||
content
|
||||
)
|
||||
conversation.job_conversations.length === 0 ? <div>{t("messaging.errors.noattachedjobs")}</div> : content
|
||||
}
|
||||
title={t("messaging.labels.selectmedia")}
|
||||
trigger="click"
|
||||
open={open}
|
||||
onOpenChange={handleVisibleChange}
|
||||
overlayClassName="media-selector-popover"
|
||||
>
|
||||
<Badge count={selectedMedia.filter((s) => s.isSelected).length}>
|
||||
<PictureFilled style={{ margin: "0 .5rem" }} />
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
.media-selector-popover {
|
||||
.ant-popover-inner-content {
|
||||
max-width: 640px;
|
||||
max-height: 480px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.media-selector-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-jobs-message {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Style images within gallery components */
|
||||
.media-selector-content img {
|
||||
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
margin: 4px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
/* Grid layout for gallery components */
|
||||
.media-selector-content .ant-image, /* Assuming gallery components use Ant Design's Image */
|
||||
.media-selector-content .gallery-container { /* Fallback for custom gallery classes */
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 4px;
|
||||
}
|
||||
@@ -4,16 +4,13 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.archive-button {
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.chat-title {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -40,13 +37,11 @@
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-send-message-button {
|
||||
.chat-send-message-button{
|
||||
margin: 0.3rem;
|
||||
padding-left: 0.5rem;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.message-icon {
|
||||
position: absolute;
|
||||
bottom: 0.1rem;
|
||||
@@ -130,37 +125,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.system {
|
||||
align-items: center;
|
||||
margin: 0.5rem 10%;
|
||||
|
||||
.message {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 10px;
|
||||
padding: 0.5rem 1rem;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
color: #555;
|
||||
width: fit-content;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.system-label {
|
||||
font-size: 0.75rem;
|
||||
color: #888;
|
||||
margin-bottom: 0.2rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.system-date {
|
||||
font-size: 0.75rem;
|
||||
color: #888;
|
||||
margin-top: 0.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.virtuoso-container {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
|
||||
@@ -2,29 +2,17 @@ import Icon from "@ant-design/icons";
|
||||
import { Tooltip } from "antd";
|
||||
import i18n from "i18next";
|
||||
import dayjs from "../../utils/day";
|
||||
import { MdClose, MdDone, MdDoneAll } from "react-icons/md";
|
||||
import { MdDone, MdDoneAll } from "react-icons/md";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
|
||||
export const renderMessage = (messages, index) => {
|
||||
const message = messages[index];
|
||||
const isSystem = message.is_system;
|
||||
|
||||
// Determine message class
|
||||
const messageClass = isSystem ? "system messages" : message.isoutbound ? "mine messages" : "yours messages";
|
||||
|
||||
// Tooltip content based on message type
|
||||
const tooltipTitle = isSystem ? (
|
||||
i18n.t("consent.text_body")
|
||||
) : (
|
||||
<DateTimeFormatter>{message.created_at}</DateTimeFormatter>
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={index} className={messageClass}>
|
||||
<div key={index} className={`${message.isoutbound ? "mine messages" : "yours messages"}`}>
|
||||
<div className="message msgmargin">
|
||||
<Tooltip title={tooltipTitle}>
|
||||
<Tooltip title={DateTimeFormatter({ children: message.created_at })}>
|
||||
<div>
|
||||
{isSystem && <span className="system-label">System</span>}
|
||||
{/* Render images if available */}
|
||||
{message.image && message.image_path?.length > 0 && (
|
||||
<div className="message-images">
|
||||
@@ -38,31 +26,20 @@ export const renderMessage = (messages, index) => {
|
||||
</div>
|
||||
)}
|
||||
{/* Render text if available */}
|
||||
{message.text && <div className="message-text">{message.text}</div>}
|
||||
{/* Render date for system messages */}
|
||||
{isSystem && (
|
||||
<div className="system-date">
|
||||
<DateTimeFormatter>{message.created_at}</DateTimeFormatter>
|
||||
</div>
|
||||
)}
|
||||
{message.text && <div>{message.text}</div>}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{/* Message status icons for non-system messages */}
|
||||
{!isSystem &&
|
||||
message.status &&
|
||||
(message.status === "sent" || message.status === "delivered" || message.status === "failed") && (
|
||||
<div className="message-status">
|
||||
<Icon
|
||||
component={message.status === "sent" ? MdDone : message.status === "delivered" ? MdDoneAll : MdClose}
|
||||
className="message-icon"
|
||||
style={message.status === "failed" ? { color: "#ff0000" } : undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Message status icons */}
|
||||
{message.status && (message.status === "sent" || message.status === "delivered") && (
|
||||
<div className="message-status">
|
||||
<Icon component={message.status === "sent" ? MdDone : MdDoneAll} className="message-icon" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* Outbound message metadata for non-system messages */}
|
||||
{!isSystem && message.isoutbound && (
|
||||
|
||||
{/* Outbound message metadata */}
|
||||
{message.isoutbound && (
|
||||
<div style={{ fontSize: 10 }}>
|
||||
{i18n.t("messaging.labels.sentby", {
|
||||
by: message.userid,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ExclamationCircleOutlined, LoadingOutlined, SendOutlined } from "@ant-design/icons";
|
||||
import { Alert, Input, Space, Spin, Tooltip } from "antd";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
|
||||
import { Alert, Input, Spin } from "antd";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -68,67 +68,48 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
|
||||
};
|
||||
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="middle">
|
||||
{isOptedOut && (
|
||||
<Tooltip title={t("consent.text_body")}>
|
||||
<Alert
|
||||
showIcon={true}
|
||||
icon={<ExclamationCircleOutlined />}
|
||||
message={t("messaging.errors.no_consent")}
|
||||
type="error"
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div className="imex-flex-row" style={{ width: "100%" }}>
|
||||
{!isOptedOut && (
|
||||
<>
|
||||
<ChatPresetsComponent disabled={isSending} className="imex-flex-row__margin" />
|
||||
<ChatMediaSelector
|
||||
disabled={isSending}
|
||||
conversation={conversation}
|
||||
selectedMedia={selectedMedia}
|
||||
setSelectedMedia={setSelectedMedia}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<span style={{ flex: 1 }}>
|
||||
<Input.TextArea
|
||||
className="imex-flex-row__margin imex-flex-row__grow"
|
||||
allowClear
|
||||
autoFocus
|
||||
ref={inputArea}
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
value={message}
|
||||
disabled={isSending || isOptedOut}
|
||||
placeholder={t("messaging.labels.typeamessage")}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onPressEnter={(event) => {
|
||||
event.preventDefault();
|
||||
if (!event.shiftKey && !isOptedOut) handleEnter();
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
{!isOptedOut && (
|
||||
<SendOutlined
|
||||
className="chat-send-message-button"
|
||||
disabled={isSending || message === "" || !message}
|
||||
onClick={handleEnter}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Spin
|
||||
style={{ display: `${isSending ? "" : "none"}` }}
|
||||
indicator={
|
||||
<LoadingOutlined
|
||||
style={{
|
||||
fontSize: 24
|
||||
}}
|
||||
spin
|
||||
/>
|
||||
}
|
||||
<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" />
|
||||
<ChatMediaSelector
|
||||
conversation={conversation}
|
||||
selectedMedia={selectedMedia}
|
||||
setSelectedMedia={setSelectedMedia}
|
||||
/>
|
||||
<span style={{ flex: 1 }}>
|
||||
<Input.TextArea
|
||||
className="imex-flex-row__margin imex-flex-row__grow"
|
||||
allowClear
|
||||
autoFocus
|
||||
ref={inputArea}
|
||||
autoSize={{ minRows: 1, maxRows: 4 }}
|
||||
value={message}
|
||||
disabled={isSending || isOptedOut}
|
||||
placeholder={t("messaging.labels.typeamessage")}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onPressEnter={(event) => {
|
||||
event.preventDefault();
|
||||
if (!event.shiftKey && !isOptedOut) handleEnter();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</span>
|
||||
<SendOutlined
|
||||
className="chat-send-message-button"
|
||||
disabled={isOptedOut || message === "" || !message}
|
||||
onClick={handleEnter}
|
||||
/>
|
||||
<Spin
|
||||
style={{ display: `${isSending ? "" : "none"}` }}
|
||||
indicator={
|
||||
<LoadingOutlined
|
||||
style={{
|
||||
fontSize: 24
|
||||
}}
|
||||
spin
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
HomeFilled,
|
||||
ImportOutlined,
|
||||
LineChartOutlined,
|
||||
OneToOneOutlined,
|
||||
PaperClipOutlined,
|
||||
PhoneOutlined,
|
||||
PlusCircleOutlined,
|
||||
@@ -25,7 +24,6 @@ import {
|
||||
TeamOutlined,
|
||||
ToolFilled,
|
||||
UnorderedListOutlined,
|
||||
UsergroupAddOutlined,
|
||||
UserOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client";
|
||||
@@ -42,7 +40,6 @@ import { RiSurveyLine } from "react-icons/ri";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js";
|
||||
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
@@ -50,10 +47,11 @@ import { signOutStart } from "../../redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import day from "../../utils/day.js";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import LockWrapper from "../lock-wrapper/lock-wrapper.component";
|
||||
import NotificationCenterContainer from "../notification-center/notification-center.container.jsx";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
||||
|
||||
// Redux mappings
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -644,32 +642,17 @@ function Header({
|
||||
label: t("menus.header.help"),
|
||||
onClick: () => window.open("https://help.imex.online/", "_blank")
|
||||
},
|
||||
{
|
||||
key: "remoteassist",
|
||||
id: "header-remote-assist",
|
||||
icon: <OneToOneOutlined />,
|
||||
label: t("menus.header.remoteassist"),
|
||||
children: [
|
||||
...(InstanceRenderManager({ imex: true, rome: false })
|
||||
? [
|
||||
{
|
||||
key: "rescue",
|
||||
id: "header-rescue",
|
||||
icon: <PlusCircleOutlined />,
|
||||
label: t("menus.header.rescueme"),
|
||||
onClick: () => window.open("https://imexrescue.com/", "_blank")
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "rescue-zoho",
|
||||
id: "header-rescue-zoho",
|
||||
icon: <UsergroupAddOutlined />,
|
||||
label: t("menus.header.rescuemezoho"),
|
||||
onClick: () => window.open("https://join.zoho.com/", "_blank")
|
||||
}
|
||||
]
|
||||
},
|
||||
...(InstanceRenderManager({ imex: true, rome: false })
|
||||
? [
|
||||
{
|
||||
key: "rescue",
|
||||
id: "header-rescue",
|
||||
icon: <CarFilled />,
|
||||
label: t("menus.header.rescueme"),
|
||||
onClick: () => window.open("https://imexrescue.com/", "_blank")
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "shiftclock",
|
||||
id: "header-shiftclock",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Form, Input, Switch } from "antd";
|
||||
import queryString from "query-string";
|
||||
import { useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
@@ -9,6 +9,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../../../firebase/firebase.utils";
|
||||
import { MARK_APPOINTMENT_ARRIVED, MARK_LATEST_APPOINTMENT_ARRIVED } from "../../../../graphql/appointments.queries";
|
||||
import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
|
||||
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
|
||||
import { insertAuditTrail } from "../../../../redux/application/application.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
|
||||
@@ -31,6 +32,7 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [markAptArrived] = useMutation(MARK_APPOINTMENT_ARRIVED);
|
||||
const [markLatestAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_ARRIVED);
|
||||
const [updateOwner] = useMutation(UPDATE_OWNER);
|
||||
const notification = useNotification();
|
||||
|
||||
const { jobId } = useParams();
|
||||
@@ -127,6 +129,24 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "intake" && job.owner && job.owner.id) {
|
||||
//Updae Owner Allow to Text
|
||||
const updateOwnerResult = await updateOwner({
|
||||
variables: {
|
||||
ownerId: job.owner.id,
|
||||
owner: { allow_text_message: values.allow_text_message }
|
||||
}
|
||||
});
|
||||
|
||||
if (!!updateOwnerResult.errors) {
|
||||
notification["error"]({
|
||||
message: t("checklist.errors.complete", {
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (!!!result.errors) {
|
||||
@@ -169,6 +189,7 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
|
||||
initialValues={{
|
||||
...(type === "intake" && {
|
||||
addToProduction: true,
|
||||
allow_text_message: job.owner && job.owner.allow_text_message,
|
||||
scheduled_completion:
|
||||
(job && job.scheduled_completion && dayjs(job.scheduled_completion)) ||
|
||||
(job &&
|
||||
@@ -207,6 +228,14 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
|
||||
>
|
||||
<Switch disabled={readOnly} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="allow_text_message"
|
||||
valuePropName="checked"
|
||||
label={t("checklist.labels.allow_text_message")}
|
||||
disabled={readOnly}
|
||||
>
|
||||
<Switch disabled={readOnly} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="scheduled_completion"
|
||||
label={t("jobs.fields.scheduled_completion")}
|
||||
|
||||
@@ -80,7 +80,7 @@ export function JobEmployeeAssignments({
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover destroyOnHidden content={popContent} open={visibility}>
|
||||
<Popover destroyTooltipOnHide content={popContent} open={visibility}>
|
||||
<Spin spinning={loading}>
|
||||
<DataLabel label={t("jobs.fields.employee_body")}>
|
||||
{body ? (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Form, Input } from "antd";
|
||||
import { Form, Input, Switch } from "antd";
|
||||
import React, { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
||||
@@ -129,6 +129,13 @@ export default function JobsCreateOwnerInfoNewComponent() {
|
||||
<Form.Item label={t("owners.fields.preferred_contact")} name={["owner", "data", "preferred_contact"]}>
|
||||
<Input disabled={!state.owner.new} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.allow_text_message")}
|
||||
valuePropName="checked"
|
||||
name={["owner", "data", "allow_text_message"]}
|
||||
>
|
||||
<Switch disabled={!state.owner.new} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function JobsCreateVehicleInfoPredefined({ disabled, form }) {
|
||||
open={open}
|
||||
placement="left"
|
||||
onOpenChange={handleOpenChange}
|
||||
destroyOnHidden
|
||||
destroyTooltipOnHide
|
||||
>
|
||||
<SearchOutlined style={{ cursor: "pointer" }} />
|
||||
</Popover>
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
|
||||
@@ -33,6 +32,7 @@ import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
|
||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
||||
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -1078,26 +1078,17 @@ export function JobsDetailHeaderActions({
|
||||
menuItems.push({
|
||||
key: "deletejob",
|
||||
id: "job-actions-deletejob",
|
||||
label:
|
||||
job.job_watchers.length === 0 ? (
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.deleteconfirm")}
|
||||
okText={t("general.labels.yes")}
|
||||
cancelText={t("general.labels.no")}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onConfirm={handleDeleteJob}
|
||||
>
|
||||
{t("menus.jobsactions.deletejob")}
|
||||
</Popconfirm>
|
||||
) : (
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.deletewatchers")}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
showCancel={false}
|
||||
>
|
||||
{t("menus.jobsactions.deletejob")}
|
||||
</Popconfirm>
|
||||
)
|
||||
label: (
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.deleteconfirm")}
|
||||
okText={t("general.labels.yes")}
|
||||
cancelText={t("general.labels.no")}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onConfirm={handleDeleteJob}
|
||||
>
|
||||
{t("menus.jobsactions.deletejob")}
|
||||
</Popconfirm>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1118,8 +1109,8 @@ export function JobsDetailHeaderActions({
|
||||
<RbacWrapper action="jobs:void" noauth>
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.voidjob")}
|
||||
okText={t("general.labels.yes")}
|
||||
cancelText={t("general.labels.no")}
|
||||
okText="Yes"
|
||||
cancelText="No"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onConfirm={handleVoidJob}
|
||||
>
|
||||
|
||||
@@ -167,18 +167,7 @@ export function JobsDetailHeaderActionsToggleProduction({
|
||||
<FormDateTimePickerComponent disabled={jobRO} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name={["actual_delivery"]}
|
||||
label={t("jobs.fields.actual_delivery")}
|
||||
rules={[
|
||||
{
|
||||
required: bodyshop.deliverchecklist.actual_delivery
|
||||
? bodyshop.deliverchecklist.actual_delivery
|
||||
: false
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Form.Item name={["actual_delivery"]} label={t("jobs.fields.actual_delivery")}>
|
||||
<FormDateTimePickerComponent disabled={jobRO} />
|
||||
</Form.Item>
|
||||
</>
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, WarningFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Card, Checkbox, Col, Divider, Row, Space, Tag, Tooltip } from "antd";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateTimeFormatter, DateTimeFormatterFunction } from "../../utils/DateFormatter";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import dayjs from "../../utils/day";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
@@ -27,6 +24,7 @@ import ProductionListColumnComment from "../production-list-columns/production-l
|
||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
import "./jobs-detail-header.styles.scss";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -40,14 +38,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
context: context,
|
||||
modal: "printCenter"
|
||||
})
|
||||
),
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
@@ -59,7 +49,7 @@ const colSpan = {
|
||||
xl: { span: 6 }
|
||||
};
|
||||
|
||||
export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail }) {
|
||||
export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
const { t } = useTranslation();
|
||||
const { notification } = useNotification();
|
||||
const [notesClamped, setNotesClamped] = useState(true);
|
||||
@@ -76,7 +66,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail })
|
||||
const handleCheckboxChange = async (field, checked) => {
|
||||
const value = checked ? dayjs().toISOString() : null;
|
||||
try {
|
||||
const ret = await updateJob({
|
||||
await updateJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: { [field]: value }
|
||||
@@ -84,16 +74,6 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail })
|
||||
refetchQueries: ["GET_JOB_BY_PK"],
|
||||
awaitRefetchQueries: true
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.jobfieldchange(
|
||||
field,
|
||||
ret.data.update_jobs.returning[0][field]
|
||||
? DateTimeFormatterFunction(ret.data.update_jobs.returning[0][field])
|
||||
: checked
|
||||
),
|
||||
type: "jobfieldchange"
|
||||
});
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: t("jobs.errors.saving", { error: error.message })
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { Form, Input, Tooltip } from "antd";
|
||||
import { CloseCircleFilled } from "@ant-design/icons";
|
||||
import { Form, Input, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||
|
||||
export default function OwnerDetailFormComponent({ form, loading, isPhone1OptedOut, isPhone2OptedOut }) {
|
||||
export default function OwnerDetailFormComponent({ form, loading }) {
|
||||
const { t } = useTranslation();
|
||||
const { getFieldValue } = form;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormFieldsChanged form={form} />
|
||||
@@ -27,7 +26,7 @@ export default function OwnerDetailFormComponent({ form, loading, isPhone1OptedO
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("owners.fields.accountingid")} name="accountingid">
|
||||
<Input disabled />
|
||||
<Input disabled/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("owners.forms.address")}>
|
||||
@@ -51,6 +50,9 @@ export default function OwnerDetailFormComponent({ form, loading, isPhone1OptedO
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("owners.forms.contact")}>
|
||||
<Form.Item label={t("owners.fields.allow_text_message")} name="allow_text_message" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_ea")}
|
||||
name="ownr_ea"
|
||||
@@ -63,55 +65,19 @@ export default function OwnerDetailFormComponent({ form, loading, isPhone1OptedO
|
||||
>
|
||||
<FormItemEmail email={getFieldValue("ownr_ea")} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("owners.fields.ownr_ph1")} style={{ marginBottom: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Form.Item
|
||||
name="ownr_ph1"
|
||||
noStyle
|
||||
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph1")]}
|
||||
>
|
||||
<Input style={{ flex: 1, minWidth: "150px" }} />
|
||||
</Form.Item>
|
||||
{isPhone1OptedOut && (
|
||||
<Tooltip title={t("consent.text_body")}>
|
||||
<CloseCircleFilled
|
||||
style={{
|
||||
color: "#ff4d4f",
|
||||
fontSize: 16,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "100%"
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_ph1")}
|
||||
name="ownr_ph1"
|
||||
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph1")]}
|
||||
>
|
||||
<FormItemPhone />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("owners.fields.ownr_ph2")} style={{ marginBottom: 0 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
|
||||
<Form.Item
|
||||
name="ownr_ph2"
|
||||
noStyle
|
||||
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph2")]}
|
||||
>
|
||||
<Input style={{ flex: 1, minWidth: "150px" }} />
|
||||
</Form.Item>
|
||||
{isPhone2OptedOut && (
|
||||
<Tooltip title={t("consent.text_body")}>
|
||||
<CloseCircleFilled
|
||||
style={{
|
||||
color: "#ff4d4f",
|
||||
fontSize: 16,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "100%"
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_ph2")}
|
||||
name="ownr_ph2"
|
||||
rules={[({ getFieldValue }) => PhoneItemFormatterValidation(getFieldValue, "ownr_ph2")]}
|
||||
>
|
||||
<FormItemPhone />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("owners.fields.preferred_contact")} name="preferred_contact">
|
||||
<Input />
|
||||
|
||||
@@ -1,115 +1,69 @@
|
||||
import { Button, Form, Popconfirm } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { DELETE_OWNER, UPDATE_OWNER } from "../../graphql/owners.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors"; // Adjust path
|
||||
import { phoneNumberOptOutService } from "../../utils/phoneOptOutService.js"; // Adjust path
|
||||
import OwnerDetailFormComponent from "./owner-detail-form.component";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { phone } from "phone"; // Import phone utility for formatting
|
||||
|
||||
// Connect to Redux to access bodyshop
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
function OwnerDetailFormContainer({ owner, refetch, bodyshop }) {
|
||||
function OwnerDetailFormContainer({ owner, refetch }) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const navigate = useNavigate();
|
||||
const history = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [optedOutPhones, setOptedOutPhones] = useState(new Set());
|
||||
const [updateOwner] = useMutation(UPDATE_OWNER);
|
||||
const [deleteOwner] = useMutation(DELETE_OWNER);
|
||||
const notification = useNotification();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
// Fetch opt-out status on mount
|
||||
useEffect(() => {
|
||||
const fetchOptOutStatus = async () => {
|
||||
if (bodyshop?.id && bodyshop?.messagingservicesid && (owner?.ownr_ph1 || owner?.ownr_ph2)) {
|
||||
const phoneNumbers = [owner.ownr_ph1, owner.ownr_ph2].filter(Boolean);
|
||||
const optOutSet = await phoneNumberOptOutService(apolloClient, bodyshop.id, phoneNumbers);
|
||||
setOptedOutPhones(optOutSet);
|
||||
} else {
|
||||
setOptedOutPhones(new Set());
|
||||
}
|
||||
};
|
||||
|
||||
fetchOptOutStatus();
|
||||
}, [apolloClient, bodyshop?.id, bodyshop?.messagingservicesid, owner?.ownr_ph1, owner?.ownr_ph2]);
|
||||
|
||||
// Reset form fields when owner changes
|
||||
useEffect(() => {
|
||||
form.setFieldsValue({
|
||||
ownr_ph1: owner?.ownr_ph1,
|
||||
ownr_ph2: owner?.ownr_ph2,
|
||||
...owner
|
||||
});
|
||||
}, [owner, form]);
|
||||
|
||||
const handleDelete = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await deleteOwner({
|
||||
variables: { id: owner.id }
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.error({
|
||||
message: t("owners.errors.deleting", {
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
});
|
||||
} else {
|
||||
notification.success({
|
||||
message: t("owners.successes.delete")
|
||||
});
|
||||
navigate(`/manage/owners`);
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
const result = await deleteOwner({
|
||||
variables: { id: owner.id }
|
||||
});
|
||||
console.log(result);
|
||||
if (result.errors) {
|
||||
notification["error"]({
|
||||
message: t("owners.errors.deleting", {
|
||||
error: error.message
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
} else {
|
||||
notification["success"]({
|
||||
message: t("owners.successes.delete")
|
||||
});
|
||||
setLoading(false);
|
||||
history(`/manage/owners`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await updateOwner({
|
||||
variables: { ownerId: owner.id, owner: values }
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.error({
|
||||
message: t("owners.errors.saving", {
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
});
|
||||
} else {
|
||||
notification.success({
|
||||
message: t("owners.successes.save")
|
||||
});
|
||||
if (refetch) await refetch();
|
||||
form.resetFields();
|
||||
}
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
const result = await updateOwner({
|
||||
variables: { ownerId: owner.id, owner: values }
|
||||
});
|
||||
|
||||
if (!!result.errors) {
|
||||
notification["error"]({
|
||||
message: t("owners.errors.saving", {
|
||||
error: error.message
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
notification["success"]({
|
||||
message: t("owners.successes.save")
|
||||
});
|
||||
|
||||
if (refetch) await refetch();
|
||||
form.resetFields();
|
||||
form.resetFields();
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -118,7 +72,6 @@ function OwnerDetailFormContainer({ owner, refetch, bodyshop }) {
|
||||
title={t("menus.header.owners")}
|
||||
extra={[
|
||||
<Popconfirm
|
||||
key="delete"
|
||||
trigger="click"
|
||||
onConfirm={handleDelete}
|
||||
disabled={owner.jobs.length !== 0}
|
||||
@@ -128,29 +81,16 @@ function OwnerDetailFormContainer({ owner, refetch, bodyshop }) {
|
||||
{t("general.actions.delete")}
|
||||
</Button>
|
||||
</Popconfirm>,
|
||||
<Button key="save" type="primary" loading={loading} onClick={() => form.submit()}>
|
||||
<Button type="primary" loading={loading} onClick={() => form.submit()}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
]}
|
||||
/>
|
||||
<Form form={form} onFinish={handleFinish} autoComplete="off" layout="vertical" initialValues={owner}>
|
||||
<OwnerDetailFormComponent
|
||||
loading={loading}
|
||||
form={form}
|
||||
isPhone1OptedOut={
|
||||
bodyshop?.messagingservicesid &&
|
||||
owner?.ownr_ph1 &&
|
||||
optedOutPhones.has(phone(owner.ownr_ph1, "CA").phoneNumber?.replace(/^\+1/, ""))
|
||||
}
|
||||
isPhone2OptedOut={
|
||||
bodyshop?.messagingservicesid &&
|
||||
owner?.ownr_ph2 &&
|
||||
optedOutPhones.has(phone(owner.ownr_ph2, "CA").phoneNumber?.replace(/^\+1/, ""))
|
||||
}
|
||||
/>
|
||||
<OwnerDetailFormComponent loading={loading} form={form} />
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(OwnerDetailFormContainer);
|
||||
export default OwnerDetailFormContainer;
|
||||
|
||||
@@ -75,7 +75,7 @@ export function PartsOrderBackorderEta({
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover destroyOnHidden content={popContent} open={visibility} disabled={disabled}>
|
||||
<Popover destroyTooltipOnHide content={popContent} open={visibility} disabled={disabled}>
|
||||
<DateFormatter>{backordered_eta}</DateFormatter>
|
||||
{isAlreadyBackordered && <CalendarFilled style={{ cursor: "pointer" }} onClick={handlePopover} />}
|
||||
{loading && <Spin />}
|
||||
|
||||
@@ -84,7 +84,7 @@ export function PartsOrderLineBackorderButton({ partsOrderStatus, partsLineId, j
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover destroyOnHidden content={popContent} open={visibility} disabled={disabled}>
|
||||
<Popover destroyTooltipOnHide content={popContent} open={visibility} disabled={disabled}>
|
||||
<Button loading={loading} onClick={handlePopover}>
|
||||
{isAlreadyBackordered ? t("parts_orders.actions.receive") : t("parts_orders.actions.backordered")}
|
||||
</Button>
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Input, Table, Typography } from "antd";
|
||||
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";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
// Commented out Associated Owners section for now
|
||||
//import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
//import { Link } from "react-router-dom";
|
||||
//import { useMemo, useState } from "react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -26,95 +20,18 @@ const mapDispatchToProps = () => ({});
|
||||
function PhoneNumberConsentList({ bodyshop, currentUser }) {
|
||||
const { t } = useTranslation();
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
// Fetch opt-out phone numbers
|
||||
const { loading: optOutLoading, data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
|
||||
const { loading, data } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
|
||||
variables: { bodyshopid: bodyshop.id, search: search ? `%${search}%` : undefined },
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
// Commented out Associated Owners section for now
|
||||
/*// Prepare phone numbers for owner query
|
||||
const phoneNumbers = useMemo(() => {
|
||||
return optOutData?.phone_number_opt_out?.map((item) => item.phone_number) || [];
|
||||
}, [optOutData?.phone_number_opt_out]);
|
||||
const allPhoneNumbers = useMemo(() => {
|
||||
const normalized = phoneNumbers;
|
||||
const withPlusOne = phoneNumbers.map((num) => `+1${num}`);
|
||||
return [...normalized, ...withPlusOne].filter(Boolean);
|
||||
}, [phoneNumbers]);
|
||||
|
||||
// Fetch owners for all phone numbers
|
||||
const { loading: ownersLoading, data: ownersData } = useQuery(SEARCH_OWNERS_BY_PHONE_NUMBERS, {
|
||||
variables: { bodyshopid: bodyshop.id, phone_numbers: allPhoneNumbers },
|
||||
skip: allPhoneNumbers.length === 0 || !bodyshop.id,
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
// Map phone numbers to their associated owners and identify phone field
|
||||
const getAssociatedOwners = (phoneNumber) => {
|
||||
if (!ownersData?.owners) return [];
|
||||
const normalizedPhone = phoneNumber.replace(/^\+1/, "");
|
||||
return ownersData.owners
|
||||
.filter(
|
||||
(owner) =>
|
||||
owner.ownr_ph1 === phoneNumber ||
|
||||
owner.ownr_ph2 === phoneNumber ||
|
||||
owner.ownr_ph1 === normalizedPhone ||
|
||||
owner.ownr_ph2 === normalizedPhone ||
|
||||
owner.ownr_ph1 === `+1${phoneNumber}` ||
|
||||
owner.ownr_ph2 === `+1${phoneNumber}`
|
||||
)
|
||||
.map((owner) => ({
|
||||
...owner,
|
||||
phoneField:
|
||||
[owner.ownr_ph1, owner.ownr_ph2].includes(phoneNumber) ||
|
||||
[owner.ownr_ph1, owner.ownr_ph2].includes(normalizedPhone) ||
|
||||
[owner.ownr_ph1, owner.ownr_ph2].includes(`+1${phoneNumber}`)
|
||||
? owner.ownr_ph1 === phoneNumber ||
|
||||
owner.ownr_ph1 === normalizedPhone ||
|
||||
owner.ownr_ph1 === `+1${phoneNumber}`
|
||||
? t("consent.phone_1")
|
||||
: t("consent.phone_2")
|
||||
: null
|
||||
}));
|
||||
};*/
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("consent.phone_number"),
|
||||
dataIndex: "phone_number",
|
||||
render: (text) => <ChatOpenButton phone={text} />,
|
||||
render: (text) => <PhoneNumberFormatter>{text}</PhoneNumberFormatter>,
|
||||
sorter: (a, b) => a.phone_number.localeCompare(b.phone_number)
|
||||
},
|
||||
// Commented out Associated Owners section for now
|
||||
/*{
|
||||
title: t("consent.associated_owners"),
|
||||
dataIndex: "phone_number",
|
||||
render: (phoneNumber) => {
|
||||
const owners = getAssociatedOwners(phoneNumber);
|
||||
if (!owners || owners.length === 0) {
|
||||
return t("consent.no_owners");
|
||||
}
|
||||
return owners.map((owner) => (
|
||||
<div key={owner.id}>
|
||||
<Space direction="horizontal">
|
||||
<Link to={"/manage/owners/" + owner.id}>
|
||||
<OwnerNameDisplay ownerObject={owner} />
|
||||
</Link>
|
||||
({owner.phoneField})
|
||||
</Space>
|
||||
</div>
|
||||
));
|
||||
},
|
||||
sorter: (a, b) => {
|
||||
const aOwners = getAssociatedOwners(a.phone_number);
|
||||
const bOwners = getAssociatedOwners(b.phone_number);
|
||||
const aName = aOwners[0] ? `${aOwners[0].ownr_fn} ${aOwners[0].ownr_ln}` : "";
|
||||
const bName = bOwners[0] ? `${bOwners[0].ownr_fn} ${bOwners[0].ownr_ln}` : "";
|
||||
return aName.localeCompare(bName);
|
||||
}
|
||||
},*/
|
||||
{
|
||||
title: t("consent.created_at"),
|
||||
dataIndex: "created_at",
|
||||
@@ -125,7 +42,6 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Paragraph>{t("consent.text_body")}</Paragraph>
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
onSearch={(value) => setSearch(value)}
|
||||
@@ -134,8 +50,8 @@ function PhoneNumberConsentList({ bodyshop, currentUser }) {
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={optOutData?.phone_number_opt_out}
|
||||
loading={optOutLoading /* || ownersLoading*/}
|
||||
dataSource={data?.phone_number_opt_out}
|
||||
loading={loading}
|
||||
rowKey="id"
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
|
||||
@@ -20,7 +20,7 @@ const Board = ({ id, className, orientation, cardSettings, ...additionalProps })
|
||||
default:
|
||||
return cardSizesVertical.small;
|
||||
}
|
||||
}, [cardSettings?.cardSize]);
|
||||
}, [cardSettings]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -101,33 +101,11 @@ const BoardContainer = ({
|
||||
async ({ draggableId, type, source, reason, mode, destination, combine }) => {
|
||||
setIsDragging(false);
|
||||
|
||||
// Validate drag type and source
|
||||
if (type !== "lane" || !source) {
|
||||
// Invalid drag type or missing source, attempt to revert if possible
|
||||
if (source) {
|
||||
dispatch(
|
||||
actions.moveCardAcrossLanes({
|
||||
fromLaneId: source.droppableId,
|
||||
toLaneId: source.droppableId,
|
||||
cardId: draggableId,
|
||||
index: source.index
|
||||
})
|
||||
);
|
||||
}
|
||||
setIsProcessing(false);
|
||||
try {
|
||||
await onDragEnd({ draggableId, type, source, reason, mode, destination, combine });
|
||||
} catch (err) {
|
||||
console.error("Error in onLaneDrag for invalid drag type or source", err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Only update drag time if it's a valid drop with a different destination
|
||||
if (type === "lane" && source && destination && !isEqual(source, destination)) {
|
||||
setDragTime(source.droppableId);
|
||||
setIsProcessing(true);
|
||||
|
||||
setDragTime(source.droppableId);
|
||||
setIsProcessing(true);
|
||||
|
||||
// Handle valid drop to a different lane or position
|
||||
if (destination && !isEqual(source, destination)) {
|
||||
dispatch(
|
||||
actions.moveCardAcrossLanes({
|
||||
fromLaneId: source.droppableId,
|
||||
@@ -136,33 +114,14 @@ const BoardContainer = ({
|
||||
index: destination.index
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Same-lane drop or no destination, revert to original position
|
||||
dispatch(
|
||||
actions.moveCardAcrossLanes({
|
||||
fromLaneId: source.droppableId,
|
||||
toLaneId: source.droppableId,
|
||||
cardId: draggableId,
|
||||
index: source.index
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await onDragEnd({ draggableId, type, source, reason, mode, destination, combine });
|
||||
} catch (err) {
|
||||
console.error("Error in onLaneDrag", err);
|
||||
// Ensure revert on error
|
||||
dispatch(
|
||||
actions.moveCardAcrossLanes({
|
||||
fromLaneId: source.droppableId,
|
||||
toLaneId: source.droppableId,
|
||||
cardId: draggableId,
|
||||
index: source.index
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
try {
|
||||
await onDragEnd({ draggableId, type, source, reason, mode, destination, combine });
|
||||
} catch (err) {
|
||||
console.error("Error in onLaneDrag", err);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[dispatch, onDragEnd, setDragTime]
|
||||
|
||||
@@ -133,9 +133,7 @@ const Lane = ({
|
||||
Item: ItemComponent
|
||||
},
|
||||
itemContent: (index, item) => <ItemWrapper>{renderDraggable(index, item)}</ItemWrapper>,
|
||||
overscan: { main: 10, reverse: 10 },
|
||||
// Ensure a minimum height for empty lanes to allow dropping
|
||||
style: renderedCards.length === 0 ? { minHeight: "5px" } : {}
|
||||
overscan: { main: 10, reverse: 10 }
|
||||
};
|
||||
|
||||
const horizontalProps = {
|
||||
@@ -151,6 +149,8 @@ const Lane = ({
|
||||
|
||||
const componentProps = orientation === "vertical" ? verticalProps : horizontalProps;
|
||||
|
||||
// If the lane is collapsed, we want to render a div instead of the virtualized list, and we want to set the height to the max height of the lane so that
|
||||
// the lane doesn't shrink when collapsed (in horizontal mode)
|
||||
const finalComponentProps = collapsed
|
||||
? orientation === "horizontal"
|
||||
? {
|
||||
@@ -161,8 +161,9 @@ const Lane = ({
|
||||
: {}
|
||||
: componentProps;
|
||||
|
||||
// Always render placeholder for empty lanes in vertical mode to ensure droppable area
|
||||
const shouldRenderPlaceholder = orientation === "vertical" ? collapsed || renderedCards.length === 0 : collapsed;
|
||||
// If the lane is horizontal and collapsed, we want to render a placeholder so that the lane doesn't shrink to 0 height and grows when
|
||||
// a card is dragged over it
|
||||
const shouldRenderPlaceholder = orientation !== "horizontal" && (collapsed || renderedCards.length === 0);
|
||||
|
||||
return (
|
||||
<HeightMemoryWrapper
|
||||
@@ -177,8 +178,8 @@ const Lane = ({
|
||||
override={orientation !== "horizontal" && (collapsed || !renderedCards.length)}
|
||||
>
|
||||
<div
|
||||
ref={laneRef}
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
ref={laneRef} // Ensure laneRef is set here
|
||||
style={{ height: "100%", width: "100%" }} // Make it scrollable
|
||||
className={`react-trello-lane ${collapsed ? "lane-collapsed" : ""}`}
|
||||
>
|
||||
<div {...provided.droppableProps} ref={provided.innerRef} style={{ ...provided.droppableProps.style }}>
|
||||
|
||||
@@ -140,7 +140,7 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
|
||||
if (record[type]) theEmployee = bodyshop.employees.find((e) => e.id === record[type]);
|
||||
|
||||
return (
|
||||
<Popover destroyOnHidden content={popContent} open={visibility}>
|
||||
<Popover destroyTooltipOnHide content={popContent} open={visibility}>
|
||||
<Spin spinning={loading}>
|
||||
{record[type] ? (
|
||||
<div>
|
||||
|
||||
@@ -107,7 +107,7 @@ export default function TimeTicketCalculatorComponent({
|
||||
open={visible}
|
||||
onOpenChange={handleOpenChange}
|
||||
placement="right"
|
||||
destroyOnHidden
|
||||
destroyTooltipOnHide
|
||||
>
|
||||
<Button onClick={(e) => e.preventDefault()}>
|
||||
<Space>
|
||||
|
||||
@@ -312,6 +312,7 @@ export const QUERY_INTAKE_CHECKLIST = gql`
|
||||
intakechecklist
|
||||
status
|
||||
owner {
|
||||
allow_text_message
|
||||
id
|
||||
}
|
||||
labhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] }) {
|
||||
|
||||
@@ -43,7 +43,6 @@ export const CONVERSATION_SUBSCRIPTION_BY_PK = gql`
|
||||
id
|
||||
status
|
||||
text
|
||||
is_system
|
||||
isoutbound
|
||||
image
|
||||
image_path
|
||||
@@ -78,7 +77,6 @@ export const GET_CONVERSATION_DETAILS = gql`
|
||||
id
|
||||
status
|
||||
text
|
||||
is_system
|
||||
isoutbound
|
||||
image
|
||||
image_path
|
||||
|
||||
@@ -874,6 +874,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
||||
}
|
||||
owner {
|
||||
id
|
||||
allow_text_message
|
||||
preferred_contact
|
||||
tax_number
|
||||
}
|
||||
@@ -2070,6 +2071,7 @@ export const QUERY_JOB_CHECKLISTS = gql`
|
||||
production_vars
|
||||
owner {
|
||||
id
|
||||
allow_text_message
|
||||
}
|
||||
bodyshop {
|
||||
id
|
||||
@@ -2426,6 +2428,7 @@ export const QUERY_PARTS_QUEUE_CARD_DETAILS = gql`
|
||||
ownr_ph2
|
||||
owner {
|
||||
id
|
||||
allow_text_message
|
||||
preferred_contact
|
||||
tax_number
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ export const QUERY_OWNER_BY_ID = gql`
|
||||
owners_by_pk(id: $id) {
|
||||
id
|
||||
accountingid
|
||||
allow_text_message
|
||||
ownr_addr1
|
||||
ownr_addr2
|
||||
ownr_co_nm
|
||||
@@ -103,6 +104,7 @@ export const QUERY_ALL_OWNERS = gql`
|
||||
query QUERY_ALL_OWNERS {
|
||||
owners {
|
||||
id
|
||||
allow_text_message
|
||||
created_at
|
||||
ownr_addr1
|
||||
ownr_addr2
|
||||
@@ -127,6 +129,7 @@ export const QUERY_ALL_OWNERS_PAGINATED = gql`
|
||||
query QUERY_ALL_OWNERS_PAGINATED($search: String, $offset: Int, $limit: Int, $order: [owners_order_by!]!) {
|
||||
search_owners(args: { search: $search }, offset: $offset, limit: $limit, order_by: $order) {
|
||||
id
|
||||
allow_text_message
|
||||
created_at
|
||||
ownr_addr1
|
||||
ownr_addr2
|
||||
|
||||
@@ -26,39 +26,3 @@ export const GET_PHONE_NUMBER_OPT_OUTS = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_PHONE_NUMBER_OPT_OUTS_BY_NUMBERS = gql`
|
||||
query GET_PHONE_NUMBER_OPT_OUTS_BY_NUMBERS($bodyshopid: uuid!, $phone_numbers: [String!]) {
|
||||
phone_number_opt_out(
|
||||
where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _in: $phone_numbers } }
|
||||
) {
|
||||
id
|
||||
bodyshopid
|
||||
phone_number
|
||||
created_at
|
||||
updated_at
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SEARCH_OWNERS_BY_PHONE_NUMBERS = gql`
|
||||
query SEARCH_OWNERS_BY_PHONE_NUMBERS($bodyshopid: uuid!, $phone_numbers: [String!]) {
|
||||
owners(
|
||||
where: {
|
||||
shopid: { _eq: $bodyshopid },
|
||||
_or: [
|
||||
{ ownr_ph1: { _in: $phone_numbers } },
|
||||
{ ownr_ph2: { _in: $phone_numbers } }
|
||||
]
|
||||
}
|
||||
) {
|
||||
id
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -114,6 +114,7 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
if (!!!job.ownerid) {
|
||||
ownerData = job.owner.data;
|
||||
ownerData.shopid = bodyshop.id;
|
||||
delete ownerData.allow_text_message;
|
||||
delete ownerData.preferred_contact;
|
||||
delete job.ownerid;
|
||||
} else {
|
||||
|
||||
@@ -92,15 +92,13 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
|
||||
});
|
||||
}
|
||||
|
||||
if (bodyshop.messagingservicesid) {
|
||||
// Add Consent Settings tab
|
||||
items.push({
|
||||
key: "consent",
|
||||
label: t("bodyshop.labels.consent_settings"),
|
||||
children: <ShopInfoConsentComponent bodyshop={bodyshop} />
|
||||
});
|
||||
}
|
||||
|
||||
// Add Consent Settings tab
|
||||
items.push({
|
||||
key: "consent",
|
||||
label: t("bodyshop.labels.consent_settings"),
|
||||
children: <ShopInfoConsentComponent bodyshop={bodyshop} />
|
||||
});
|
||||
|
||||
return (
|
||||
<RbacWrapper action="shop:config">
|
||||
<Tabs activeKey={search.tab} onChange={(key) => history({ search: `?tab=${key}` })} items={items} />
|
||||
|
||||
@@ -335,12 +335,20 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
}
|
||||
|
||||
try {
|
||||
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
||||
window.$crisp.push(["set", "session:segments", [[`region:${payload.region_config}`]]]);
|
||||
if (authRecord[0] && authRecord[0].user.validemail) {
|
||||
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
||||
}
|
||||
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
args: [],
|
||||
imex: () => {
|
||||
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
||||
window.$crisp.push(["set", "session:segments", [[`region:${payload.region_config}`]]]);
|
||||
if (authRecord[0] && authRecord[0].user.validemail) {
|
||||
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
||||
}
|
||||
},
|
||||
rome: () => {
|
||||
window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname });
|
||||
}
|
||||
});
|
||||
payload.features?.allAccess === true
|
||||
? window.$crisp.push(["set", "session:segments", [["allAccess"]]])
|
||||
: (() => {
|
||||
@@ -351,14 +359,6 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
);
|
||||
window.$crisp.push(["set", "session:segments", [["basic", ...featureKeys]]]);
|
||||
})();
|
||||
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
args: [],
|
||||
rome: () => {
|
||||
window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("Couldnt find $crisp.", error.message);
|
||||
}
|
||||
|
||||
@@ -775,6 +775,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"addtoproduction": "Add Job to Production?",
|
||||
"allow_text_message": "Permission to Text?",
|
||||
"checklist": "Checklist",
|
||||
"printpack": "Job Intake Print Pack",
|
||||
"removefromproduction": "Remove Job from Production?"
|
||||
@@ -2031,10 +2032,9 @@
|
||||
"stands": "Stands",
|
||||
"waived": "Waived"
|
||||
},
|
||||
"deleteconfirm": "Are you sure you want to delete this Job? This cannot be undone.",
|
||||
"deleteconfirm": "Are you sure you want to delete this Job? This cannot be undone. ",
|
||||
"deletedelivery": "Delete Delivery Checklist",
|
||||
"deleteintake": "Delete Intake Checklist",
|
||||
"deletewatchers": "Remove Watchers before deleting this Job.",
|
||||
"deliverchecklist": "Deliver Checklist",
|
||||
"difference": "Difference",
|
||||
"diskscan": "Scan Disk for Estimates",
|
||||
@@ -2302,10 +2302,8 @@
|
||||
"productionlist": "Production Board - List",
|
||||
"readyjobs": "Ready Jobs",
|
||||
"recent": "Recent Items",
|
||||
"remoteassist": "Remote Assist",
|
||||
"reportcenter": "Report Center",
|
||||
"rescueme": "Rescue Me!",
|
||||
"rescuemezoho": "Remote Me In!",
|
||||
"rescueme": "Rescue me!",
|
||||
"schedule": "Schedule",
|
||||
"scoreboard": "Scoreboard",
|
||||
"search": {
|
||||
@@ -2381,7 +2379,7 @@
|
||||
"invalidphone": "The phone number is invalid. Unable to open conversation. ",
|
||||
"noattachedjobs": "No Jobs have been associated to this conversation. ",
|
||||
"updatinglabel": "Error updating label. {{error}}",
|
||||
"no_consent": "This phone number has opted-out of Messaging."
|
||||
"no_consent": "This phone number has not consented to receive messages."
|
||||
},
|
||||
"labels": {
|
||||
"addlabel": "Add a label to this conversation.",
|
||||
@@ -2398,7 +2396,7 @@
|
||||
"sentby": "Sent by {{by}} at {{time}}",
|
||||
"typeamessage": "Send a message...",
|
||||
"unarchive": "Unarchive",
|
||||
"no_consent": "Opted-out"
|
||||
"no_consent": "No Consent"
|
||||
},
|
||||
"render": {
|
||||
"conversation_list": "Conversation List"
|
||||
@@ -2523,6 +2521,7 @@
|
||||
"fields": {
|
||||
"accountingid": "Accounting ID",
|
||||
"address": "Address",
|
||||
"allow_text_message": "Permission to Text?",
|
||||
"name": "Name",
|
||||
"note": "Owner Note",
|
||||
"ownr_addr1": "Address",
|
||||
@@ -3870,12 +3869,8 @@
|
||||
},
|
||||
"consent": {
|
||||
"phone_number": "Phone Number",
|
||||
"associated_owners": "Associated Owners",
|
||||
"created_at": "Opt-Out Date",
|
||||
"no_owners": "No Associated Owners",
|
||||
"phone_1": "Phone 1",
|
||||
"phone_2": "Phone 2",
|
||||
"text_body": "Users can opt out of receiving SMS messages by replying with keywords such as STOP, UNSUBSCRIBE, CANCEL, END, QUIT, STOPALL, REVOKE and OPTOUT. To opt back in, users can reply with START, YES, or UNSTOP. Even after opting out, users can still send messages to us, which will be received and processed as needed. Ensure customers are informed to reply with these keywords to manage their messaging preferences. After opting out, users receive a confirmation message and will not receive further messages until they opt back in."
|
||||
"status": "Consent Status",
|
||||
"created_at": "Created At"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Phone Number Opt-Out List"
|
||||
|
||||
@@ -656,7 +656,6 @@
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"consent_settings": "",
|
||||
"2tiername": "",
|
||||
"2tiersetup": "",
|
||||
"2tiersource": "",
|
||||
@@ -775,6 +774,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"addtoproduction": "",
|
||||
"allow_text_message": "",
|
||||
"checklist": "",
|
||||
"printpack": "",
|
||||
"removefromproduction": ""
|
||||
@@ -2034,7 +2034,6 @@
|
||||
"deleteconfirm": "",
|
||||
"deletedelivery": "",
|
||||
"deleteintake": "",
|
||||
"deletewatchers": "",
|
||||
"deliverchecklist": "",
|
||||
"difference": "",
|
||||
"diskscan": "",
|
||||
@@ -2302,10 +2301,8 @@
|
||||
"productionlist": "",
|
||||
"readyjobs": "",
|
||||
"recent": "",
|
||||
"remoteassist": "",
|
||||
"reportcenter": "",
|
||||
"rescueme": "",
|
||||
"rescuemezoho": "",
|
||||
"schedule": "Programar",
|
||||
"scoreboard": "",
|
||||
"search": {
|
||||
@@ -2380,8 +2377,7 @@
|
||||
"errors": {
|
||||
"invalidphone": "",
|
||||
"noattachedjobs": "",
|
||||
"updatinglabel": "",
|
||||
"no_consent": ""
|
||||
"updatinglabel": ""
|
||||
},
|
||||
"labels": {
|
||||
"addlabel": "",
|
||||
@@ -2397,8 +2393,7 @@
|
||||
"selectmedia": "",
|
||||
"sentby": "",
|
||||
"typeamessage": "Enviar un mensaje...",
|
||||
"unarchive": "",
|
||||
"no_consent": ""
|
||||
"unarchive": ""
|
||||
},
|
||||
"render": {
|
||||
"conversation_list": ""
|
||||
@@ -2503,8 +2498,7 @@
|
||||
},
|
||||
"tooltips": {
|
||||
"job-watchers": "",
|
||||
"not-employee": "",
|
||||
"not-employee-notifications": ""
|
||||
"not-employee": ""
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
@@ -2525,6 +2519,7 @@
|
||||
"fields": {
|
||||
"accountingid": "",
|
||||
"address": "Dirección",
|
||||
"allow_text_message": "Permiso de texto?",
|
||||
"name": "Nombre",
|
||||
"note": "",
|
||||
"ownr_addr1": "Dirección",
|
||||
@@ -3869,18 +3864,6 @@
|
||||
"validation": {
|
||||
"unique_vendor_name": ""
|
||||
}
|
||||
},
|
||||
"consent": {
|
||||
"phone_number": "",
|
||||
"associated_owners": "",
|
||||
"created_at": "",
|
||||
"no_owners": "",
|
||||
"phone_1": "",
|
||||
"phone_2": "",
|
||||
"text_body": ""
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,7 +656,6 @@
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"consent_settings": "",
|
||||
"2tiername": "",
|
||||
"2tiersetup": "",
|
||||
"2tiersource": "",
|
||||
@@ -775,6 +774,7 @@
|
||||
},
|
||||
"labels": {
|
||||
"addtoproduction": "",
|
||||
"allow_text_message": "",
|
||||
"checklist": "",
|
||||
"printpack": "",
|
||||
"removefromproduction": ""
|
||||
@@ -2034,7 +2034,6 @@
|
||||
"deleteconfirm": "",
|
||||
"deletedelivery": "",
|
||||
"deleteintake": "",
|
||||
"deletewatchers": "",
|
||||
"deliverchecklist": "",
|
||||
"difference": "",
|
||||
"diskscan": "",
|
||||
@@ -2302,10 +2301,8 @@
|
||||
"productionlist": "",
|
||||
"readyjobs": "",
|
||||
"recent": "",
|
||||
"remoteassist": "",
|
||||
"reportcenter": "",
|
||||
"rescueme": "",
|
||||
"rescuemezoho": "",
|
||||
"schedule": "Programme",
|
||||
"scoreboard": "",
|
||||
"search": {
|
||||
@@ -2380,8 +2377,7 @@
|
||||
"errors": {
|
||||
"invalidphone": "",
|
||||
"noattachedjobs": "",
|
||||
"updatinglabel": "",
|
||||
"no_consent": ""
|
||||
"updatinglabel": ""
|
||||
},
|
||||
"labels": {
|
||||
"addlabel": "",
|
||||
@@ -2397,8 +2393,7 @@
|
||||
"selectmedia": "",
|
||||
"sentby": "",
|
||||
"typeamessage": "Envoyer un message...",
|
||||
"unarchive": "",
|
||||
"no_consent": ""
|
||||
"unarchive": ""
|
||||
},
|
||||
"render": {
|
||||
"conversation_list": ""
|
||||
@@ -2503,8 +2498,7 @@
|
||||
},
|
||||
"tooltips": {
|
||||
"job-watchers": "",
|
||||
"not-employee": "",
|
||||
"not-employee-notifications": ""
|
||||
"not-employee": ""
|
||||
}
|
||||
},
|
||||
"owner": {
|
||||
@@ -2525,6 +2519,7 @@
|
||||
"fields": {
|
||||
"accountingid": "",
|
||||
"address": "Adresse",
|
||||
"allow_text_message": "Autorisation de texte?",
|
||||
"name": "Prénom",
|
||||
"note": "",
|
||||
"ownr_addr1": "Adresse",
|
||||
@@ -3869,18 +3864,6 @@
|
||||
"validation": {
|
||||
"unique_vendor_name": ""
|
||||
}
|
||||
},
|
||||
"consent": {
|
||||
"phone_number": "Phone Number",
|
||||
"associated_owners": "Associated Owners",
|
||||
"created_at": "Opt-Out Date",
|
||||
"no_owners": "No Associated Owners",
|
||||
"phone_1": "Phone 1",
|
||||
"phone_2": "Phone 2",
|
||||
"text_body": ""
|
||||
},
|
||||
"settings": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Tooltip } from "antd";
|
||||
import dayjs from "../utils/day";
|
||||
import React from "react";
|
||||
|
||||
export function DateFormatter(props) {
|
||||
return props.children ? dayjs(props.children).format(props.includeDay ? "ddd MM/DD/YYYY" : "MM/DD/YYYY") : null;
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { phone } from "phone";
|
||||
import { GET_PHONE_NUMBER_OPT_OUTS_BY_NUMBERS } from "../graphql/phone-number-opt-out.queries";
|
||||
|
||||
/**
|
||||
* Check if phone numbers are opted out for a given bodyshop
|
||||
* @param {Object} apolloClient - Apollo Client instance
|
||||
* @param {string} bodyshopId - The ID of the bodyshop
|
||||
* @param {string[]} phoneNumbers - Array of phone numbers to check
|
||||
* @returns {Promise<Set<string>>} - Set of normalized opted-out phone numbers
|
||||
*/
|
||||
export const phoneNumberOptOutService = async (apolloClient, bodyshopId, phoneNumbers) => {
|
||||
if (!apolloClient || !bodyshopId || !phoneNumbers?.length) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
// Normalize phone numbers (remove +1 for CA numbers)
|
||||
const normalizedPhones = phoneNumbers
|
||||
.filter(Boolean)
|
||||
.map((num) => phone(num, "CA").phoneNumber?.replace(/^\+1/, ""))
|
||||
.filter(Boolean);
|
||||
|
||||
if (!normalizedPhones.length) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
const optedOutPhones = new Set();
|
||||
|
||||
try {
|
||||
const { data } = await apolloClient.query({
|
||||
query: GET_PHONE_NUMBER_OPT_OUTS_BY_NUMBERS,
|
||||
variables: {
|
||||
bodyshopid: bodyshopId,
|
||||
phone_numbers: normalizedPhones // Array of phone numbers
|
||||
},
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
if (data?.phone_number_opt_out?.length) {
|
||||
data.phone_number_opt_out.forEach((optOut) => {
|
||||
optedOutPhones.add(optOut.phone_number);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking opt-out statuses:", error);
|
||||
}
|
||||
|
||||
return optedOutPhones;
|
||||
};
|
||||
@@ -4742,7 +4742,6 @@
|
||||
- id
|
||||
- image
|
||||
- image_path
|
||||
- is_system
|
||||
- isoutbound
|
||||
- msid
|
||||
- read
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."messages" add column "is_system" boolean
|
||||
-- null default 'false';
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table "public"."messages" add column "is_system" boolean
|
||||
null default 'false';
|
||||
@@ -10028,6 +10028,25 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>allow_text_message</name>
|
||||
<description/>
|
||||
<comment/>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-ES</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>checklist</name>
|
||||
<description/>
|
||||
@@ -32981,6 +33000,25 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>allow_text_message</name>
|
||||
<description/>
|
||||
<comment/>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-ES</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>name</name>
|
||||
<description/>
|
||||
|
||||
287
package-lock.json
generated
287
package-lock.json
generated
@@ -9,14 +9,14 @@
|
||||
"version": "0.2.0",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.817.0",
|
||||
"@aws-sdk/client-elasticache": "^3.817.0",
|
||||
"@aws-sdk/client-s3": "^3.817.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.817.0",
|
||||
"@aws-sdk/client-ses": "^3.817.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.817.0",
|
||||
"@aws-sdk/lib-storage": "^3.817.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.817.0",
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.812.0",
|
||||
"@aws-sdk/client-elasticache": "^3.812.0",
|
||||
"@aws-sdk/client-s3": "^3.812.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.812.0",
|
||||
"@aws-sdk/client-ses": "^3.812.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.812.0",
|
||||
"@aws-sdk/lib-storage": "^3.812.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.812.0",
|
||||
"@opensearch-project/opensearch": "^2.13.0",
|
||||
"@socket.io/admin-ui": "^0.5.1",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"crisp-status-reporter": "^1.2.2",
|
||||
"dd-trace": "^5.53.0",
|
||||
"dd-trace": "^5.52.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.1",
|
||||
@@ -45,7 +45,7 @@
|
||||
"juice": "^11.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.6.0",
|
||||
"moment-timezone": "^0.5.48",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-persist": "^4.0.4",
|
||||
"nodemailer": "^6.10.0",
|
||||
@@ -284,24 +284,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-cloudwatch-logs": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.817.0.tgz",
|
||||
"integrity": "sha512-dbR4YZZ2wulMzblgSSE43yd9jgbXDMSrZS7w7r0DqDNAbsXrp79qU2CvA+lb47wGpDxMNppgvoCMu5kcIP5gXw==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.812.0.tgz",
|
||||
"integrity": "sha512-SLvqaMwRviAwb+z4XAq2QmlbUjr7rXN6zAEr4/x2ltyrsxEV95gBo0KHeroAsWhd4eD19USjAgg64KJgvUtNGw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/credential-provider-node": "3.817.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/credential-provider-node": "3.812.0",
|
||||
"@aws-sdk/middleware-host-header": "3.804.0",
|
||||
"@aws-sdk/middleware-logger": "3.804.0",
|
||||
"@aws-sdk/middleware-recursion-detection": "3.804.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.816.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.812.0",
|
||||
"@aws-sdk/region-config-resolver": "3.808.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@aws-sdk/util-endpoints": "3.808.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.804.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.816.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.812.0",
|
||||
"@smithy/config-resolver": "^4.1.2",
|
||||
"@smithy/core": "^3.3.3",
|
||||
"@smithy/eventstream-serde-browser": "^4.0.2",
|
||||
@@ -352,24 +352,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-elasticache": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.817.0.tgz",
|
||||
"integrity": "sha512-TO1Zfv3racKQsRoll4owV2q4kNcw1x64D19KFrWd87rQ517ahbXRcPpaKOqe9CYG1Zo3SIzRySaJLoPftXDfRQ==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-elasticache/-/client-elasticache-3.812.0.tgz",
|
||||
"integrity": "sha512-o1KC5Glo3c0T/RN2XBanHu40k3M99MJyq+e/02tIMgEGKIPmnvB8A8muE2F3rQ2A0qLCxvjhm+kprlmDwzpryw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/credential-provider-node": "3.817.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/credential-provider-node": "3.812.0",
|
||||
"@aws-sdk/middleware-host-header": "3.804.0",
|
||||
"@aws-sdk/middleware-logger": "3.804.0",
|
||||
"@aws-sdk/middleware-recursion-detection": "3.804.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.816.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.812.0",
|
||||
"@aws-sdk/region-config-resolver": "3.808.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@aws-sdk/util-endpoints": "3.808.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.804.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.816.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.812.0",
|
||||
"@smithy/config-resolver": "^4.1.2",
|
||||
"@smithy/core": "^3.3.3",
|
||||
"@smithy/fetch-http-handler": "^5.0.2",
|
||||
@@ -403,32 +403,32 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-s3": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.817.0.tgz",
|
||||
"integrity": "sha512-nZyjhlLMEXDs0ofWbpikI8tKoeKuuSgYcIb6eEZJk90Nt5HkkXn6nkWOs/kp2FdhpoGJyTILOVsDgdm7eutnLA==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.812.0.tgz",
|
||||
"integrity": "sha512-kHgw9JDXNPLa/mHtWpOd5btBVXFSe+wwp1Ed9+bqz9uLkv0iV4joZrdQwnydkO8zlTs60Sc5ez+P2OiZ76i2Qg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha1-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/credential-provider-node": "3.817.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/credential-provider-node": "3.812.0",
|
||||
"@aws-sdk/middleware-bucket-endpoint": "3.808.0",
|
||||
"@aws-sdk/middleware-expect-continue": "3.804.0",
|
||||
"@aws-sdk/middleware-flexible-checksums": "3.816.0",
|
||||
"@aws-sdk/middleware-flexible-checksums": "3.812.0",
|
||||
"@aws-sdk/middleware-host-header": "3.804.0",
|
||||
"@aws-sdk/middleware-location-constraint": "3.804.0",
|
||||
"@aws-sdk/middleware-logger": "3.804.0",
|
||||
"@aws-sdk/middleware-recursion-detection": "3.804.0",
|
||||
"@aws-sdk/middleware-sdk-s3": "3.816.0",
|
||||
"@aws-sdk/middleware-sdk-s3": "3.812.0",
|
||||
"@aws-sdk/middleware-ssec": "3.804.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.816.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.812.0",
|
||||
"@aws-sdk/region-config-resolver": "3.808.0",
|
||||
"@aws-sdk/signature-v4-multi-region": "3.816.0",
|
||||
"@aws-sdk/signature-v4-multi-region": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@aws-sdk/util-endpoints": "3.808.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.804.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.816.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.812.0",
|
||||
"@aws-sdk/xml-builder": "3.804.0",
|
||||
"@smithy/config-resolver": "^4.1.2",
|
||||
"@smithy/core": "^3.3.3",
|
||||
@@ -470,24 +470,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-secrets-manager": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.817.0.tgz",
|
||||
"integrity": "sha512-Hx74xmJo9xPeHRFtFGdsT5qFx6p9V13ptQ3HICnkmcbtA+CX8soTuc5mglkp9vTdTjvRwKVAmQhx6NPf9ELcjQ==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.812.0.tgz",
|
||||
"integrity": "sha512-RyGzi7kkacjPd0QgVjw6OYvZVvuqtd1wRwG0Aek32dPUYu8eOs9FDaqBsDnNIqdw+lAqC/pKIOPYWtLu2OxE0Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/credential-provider-node": "3.817.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/credential-provider-node": "3.812.0",
|
||||
"@aws-sdk/middleware-host-header": "3.804.0",
|
||||
"@aws-sdk/middleware-logger": "3.804.0",
|
||||
"@aws-sdk/middleware-recursion-detection": "3.804.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.816.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.812.0",
|
||||
"@aws-sdk/region-config-resolver": "3.808.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@aws-sdk/util-endpoints": "3.808.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.804.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.816.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.812.0",
|
||||
"@smithy/config-resolver": "^4.1.2",
|
||||
"@smithy/core": "^3.3.3",
|
||||
"@smithy/fetch-http-handler": "^5.0.2",
|
||||
@@ -535,24 +535,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-ses": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.817.0.tgz",
|
||||
"integrity": "sha512-cf2FsdcTT5HiOFOnWk3tzRc84iXcrUNXe4O4KaH75tRToBuQkTaidPI/K9wHnOybNDEkkCcgJo9skv4ftz8qYA==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.812.0.tgz",
|
||||
"integrity": "sha512-7JUS2u0AKMYiEmRrxAYQj8ifFwVUgMAHt5H/KjMhh+1El0NqAQDt3JLD4Asmzy7/TvTAWZfk5np2LQPNB2wZpw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/credential-provider-node": "3.817.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/credential-provider-node": "3.812.0",
|
||||
"@aws-sdk/middleware-host-header": "3.804.0",
|
||||
"@aws-sdk/middleware-logger": "3.804.0",
|
||||
"@aws-sdk/middleware-recursion-detection": "3.804.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.816.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.812.0",
|
||||
"@aws-sdk/region-config-resolver": "3.808.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@aws-sdk/util-endpoints": "3.808.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.804.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.816.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.812.0",
|
||||
"@smithy/config-resolver": "^4.1.2",
|
||||
"@smithy/core": "^3.3.3",
|
||||
"@smithy/fetch-http-handler": "^5.0.2",
|
||||
@@ -586,23 +586,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-sso": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.817.0.tgz",
|
||||
"integrity": "sha512-fCh5rUHmWmWDvw70NNoWpE5+BRdtNi45kDnIoeoszqVg7UKF79SlG+qYooUT52HKCgDNHqgbWaXxMOSqd2I/OQ==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.812.0.tgz",
|
||||
"integrity": "sha512-O//smQRj1+RXELB7xX54s5pZB0V69KHXpUZmz8V+8GAYO1FKTHfbpUgK+zyMNb+lFZxG9B69yl8pWPZ/K8bvxA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/middleware-host-header": "3.804.0",
|
||||
"@aws-sdk/middleware-logger": "3.804.0",
|
||||
"@aws-sdk/middleware-recursion-detection": "3.804.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.816.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.812.0",
|
||||
"@aws-sdk/region-config-resolver": "3.808.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@aws-sdk/util-endpoints": "3.808.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.804.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.816.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.812.0",
|
||||
"@smithy/config-resolver": "^4.1.2",
|
||||
"@smithy/core": "^3.3.3",
|
||||
"@smithy/fetch-http-handler": "^5.0.2",
|
||||
@@ -635,9 +635,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/core": {
|
||||
"version": "3.816.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.816.0.tgz",
|
||||
"integrity": "sha512-Lx50wjtyarzKpMFV6V+gjbSZDgsA/71iyifbClGUSiNPoIQ4OCV0KVOmAAj7mQRVvGJqUMWKVM+WzK79CjbjWA==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.812.0.tgz",
|
||||
"integrity": "sha512-myWA9oHMBVDObKrxG+puAkIGs8igcWInQ1PWCRTS/zN4BkhUMFjjh/JPV/4Vzvtvj5E36iujq2WtlrDLl1PpOw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
@@ -657,12 +657,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-env": {
|
||||
"version": "3.816.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.816.0.tgz",
|
||||
"integrity": "sha512-wUJZwRLe+SxPxRV9AENYBLrJZRrNIo+fva7ZzejsC83iz7hdfq6Rv6B/aHEdPwG/nQC4+q7UUvcRPlomyrpsBA==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.812.0.tgz",
|
||||
"integrity": "sha512-Ge7IEu06ANurGBZx39q9CNN/ncqb1K8lpKZCY969uNWO0/7YPhnplrRJGMZYIS35nD2mBm3ortEKjY/wMZZd5g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/property-provider": "^4.0.2",
|
||||
"@smithy/types": "^4.2.0",
|
||||
@@ -673,12 +673,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-http": {
|
||||
"version": "3.816.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.816.0.tgz",
|
||||
"integrity": "sha512-gcWGzMQ7yRIF+ljTkR8Vzp7727UY6cmeaPrFQrvcFB8PhOqWpf7g0JsgOf5BSaP8CkkSQcTQHc0C5ZYAzUFwPg==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.812.0.tgz",
|
||||
"integrity": "sha512-Vux2U42vPGXeE407Lp6v3yVA65J7hBO9rB67LXshyGVi7VZLAYWc4mrZxNJNqabEkjcDEmMQQakLPT6zc5SvFw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/fetch-http-handler": "^5.0.2",
|
||||
"@smithy/node-http-handler": "^4.0.4",
|
||||
@@ -694,18 +694,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-ini": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.817.0.tgz",
|
||||
"integrity": "sha512-kyEwbQyuXE+phWVzloMdkFv6qM6NOon+asMXY5W0fhDKwBz9zQLObDRWBrvQX9lmqq8BbDL1sCfZjOh82Y+RFw==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.812.0.tgz",
|
||||
"integrity": "sha512-oltqGvQ488xtPY5wrNjbD+qQYYkuCjn30IDE1qKMxJ58EM6UVTQl3XV44Xq07xfF5gKwVJQkfIyOkRAguOVybg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/credential-provider-env": "3.816.0",
|
||||
"@aws-sdk/credential-provider-http": "3.816.0",
|
||||
"@aws-sdk/credential-provider-process": "3.816.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.817.0",
|
||||
"@aws-sdk/credential-provider-web-identity": "3.817.0",
|
||||
"@aws-sdk/nested-clients": "3.817.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/credential-provider-env": "3.812.0",
|
||||
"@aws-sdk/credential-provider-http": "3.812.0",
|
||||
"@aws-sdk/credential-provider-process": "3.812.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.812.0",
|
||||
"@aws-sdk/credential-provider-web-identity": "3.812.0",
|
||||
"@aws-sdk/nested-clients": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/credential-provider-imds": "^4.0.4",
|
||||
"@smithy/property-provider": "^4.0.2",
|
||||
@@ -718,17 +718,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-node": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.817.0.tgz",
|
||||
"integrity": "sha512-b5mz7av0Lhavs1Bz3Zb+jrs0Pki93+8XNctnVO0drBW98x1fM4AR38cWvGbM/w9F9Q0/WEH3TinkmrMPrP4T/w==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.812.0.tgz",
|
||||
"integrity": "sha512-SnvSWBP6cr9nqx784eETnL2Zl7ZnMB/oJgFVEG1aejAGbT1H9gTpMwuUsBXk4u/mEYe3f1lh1Wqo+HwDgNkfrg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-provider-env": "3.816.0",
|
||||
"@aws-sdk/credential-provider-http": "3.816.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.817.0",
|
||||
"@aws-sdk/credential-provider-process": "3.816.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.817.0",
|
||||
"@aws-sdk/credential-provider-web-identity": "3.817.0",
|
||||
"@aws-sdk/credential-provider-env": "3.812.0",
|
||||
"@aws-sdk/credential-provider-http": "3.812.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.812.0",
|
||||
"@aws-sdk/credential-provider-process": "3.812.0",
|
||||
"@aws-sdk/credential-provider-sso": "3.812.0",
|
||||
"@aws-sdk/credential-provider-web-identity": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/credential-provider-imds": "^4.0.4",
|
||||
"@smithy/property-provider": "^4.0.2",
|
||||
@@ -741,12 +741,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-process": {
|
||||
"version": "3.816.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.816.0.tgz",
|
||||
"integrity": "sha512-9Tm+AxMoV2Izvl5b9tyMQRbBwaex8JP06HN7ZeCXgC5sAsSN+o8dsThnEhf8jKN+uBpT6CLWKN1TXuUMrAmW1A==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.812.0.tgz",
|
||||
"integrity": "sha512-YI8bb153XeEOb59F9KtTZEwDAc14s2YHZz58+OFiJ2udnKsPV87mNiFhJPW6ba9nmOLXVat5XDcwtVT1b664wg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/property-provider": "^4.0.2",
|
||||
"@smithy/shared-ini-file-loader": "^4.0.2",
|
||||
@@ -758,14 +758,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-sso": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.817.0.tgz",
|
||||
"integrity": "sha512-gFUAW3VmGvdnueK1bh6TOcRX+j99Xm0men1+gz3cA4RE+rZGNy1Qjj8YHlv0hPwI9OnTPZquvPzA5fkviGREWg==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.812.0.tgz",
|
||||
"integrity": "sha512-ODsPcNhgiO6GOa82TVNskM97mml9rioe9Cbhemz48lkfDQPv1u06NaCR0o3FsvprX1sEhMvJTR3sE1fyEOzvJQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sso": "3.817.0",
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/token-providers": "3.817.0",
|
||||
"@aws-sdk/client-sso": "3.812.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/token-providers": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/property-provider": "^4.0.2",
|
||||
"@smithy/shared-ini-file-loader": "^4.0.2",
|
||||
@@ -777,13 +777,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/credential-provider-web-identity": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.817.0.tgz",
|
||||
"integrity": "sha512-A2kgkS9g6NY0OMT2f2EdXHpL17Ym81NhbGnQ8bRXPqESIi7TFypFD2U6osB2VnsFv+MhwM+Ke4PKXSmLun22/A==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.812.0.tgz",
|
||||
"integrity": "sha512-E9Bmiujvm/Hp9DM/Vc1S+D0pQbx8/x4dR/zyAEZU9EoRq0duQOQ1reWYWbebYmL1OklcVpTfKV0a/VCwuAtGSg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/nested-clients": "3.817.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/nested-clients": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/property-provider": "^4.0.2",
|
||||
"@smithy/types": "^4.2.0",
|
||||
@@ -794,9 +794,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/lib-storage": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.817.0.tgz",
|
||||
"integrity": "sha512-2zOO8+2EmiS049PjLSNdqmmZMQj7fzE1hZJ70A94vO+KNaVhVZYuMOOiOmwMw6ePkTCcFwK40vZIIXwEQQ1v1g==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.812.0.tgz",
|
||||
"integrity": "sha512-z37ykuXQXfGO7dqQFbEnj1Wu9UwUUXpZhr4iWXsehbIzSqyl5FiCMp0cI5XK8jLVACCfSCssZCz6QD4oDYdKlQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/abort-controller": "^4.0.2",
|
||||
@@ -811,7 +811,7 @@
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.817.0"
|
||||
"@aws-sdk/client-s3": "^3.812.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-bucket-endpoint": {
|
||||
@@ -848,15 +848,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-flexible-checksums": {
|
||||
"version": "3.816.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.816.0.tgz",
|
||||
"integrity": "sha512-kftcwDxB/VoCBsUiRgkm5CIuKbTfCN1WLPbis9LRwX3kQhKgGVxG2gG78SHk4TBB0qviWVAd/t+i/KaUgwiAcA==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.812.0.tgz",
|
||||
"integrity": "sha512-/ayAooUZvV1GTomNMrfbhjUHAEaz0Wmio3lKyaTJsW4WdLJXBuzdo57YADRmYYUqx6awzJ6VJ6HGc1Uc6tOlbw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/crc32": "5.2.0",
|
||||
"@aws-crypto/crc32c": "5.2.0",
|
||||
"@aws-crypto/util": "5.2.0",
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/is-array-buffer": "^4.0.0",
|
||||
"@smithy/node-config-provider": "^4.1.1",
|
||||
@@ -930,12 +930,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-sdk-s3": {
|
||||
"version": "3.816.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.816.0.tgz",
|
||||
"integrity": "sha512-jJ+EAXM7gnOwiCM6rrl4AUNY5urmtIsX7roTkxtb4DevJxcS+wFYRRg3/j33fQbuxQZrvk21HqxyZYx5UH70PA==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.812.0.tgz",
|
||||
"integrity": "sha512-e8AqRRIaTsunL1hqtO1hksa9oTYdsIbfezHUyVpPGugUIB1lMqPt/DlBsanI85OzUD711UfNSEcZ1mqAxpDOoA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@aws-sdk/util-arn-parser": "3.804.0",
|
||||
"@smithy/core": "^3.3.3",
|
||||
@@ -969,12 +969,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/middleware-user-agent": {
|
||||
"version": "3.816.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.816.0.tgz",
|
||||
"integrity": "sha512-bHRSlWZ0xDsFR8E2FwDb//0Ff6wMkVx4O+UKsfyNlAbtqCiiHRt5ANNfKPafr95cN2CCxLxiPvFTFVblQM5TsQ==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.812.0.tgz",
|
||||
"integrity": "sha512-r+HFwtSvnAs6Fydp4mijylrTX0og9p/xfxOcKsqhMuk3HpZAIcf9sSjRQI6MBusYklg7pnM4sGEnPAZIrdRotA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@aws-sdk/util-endpoints": "3.808.0",
|
||||
"@smithy/core": "^3.3.3",
|
||||
@@ -987,23 +987,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/nested-clients": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.817.0.tgz",
|
||||
"integrity": "sha512-vQ2E06A48STJFssueJQgxYD8lh1iGJoLJnHdshRDWOQb8gy1wVQR+a7MkPGhGR6lGoS0SCnF/Qp6CZhnwLsqsQ==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.812.0.tgz",
|
||||
"integrity": "sha512-FS/fImbEpJU3cXtBGR9fyVd+CP51eNKlvTMi3f4/6lSk3RmHjudNC9yEF/og3jtpT3O+7vsNOUW9mHco5IjdQQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
"@aws-crypto/sha256-js": "5.2.0",
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/core": "3.812.0",
|
||||
"@aws-sdk/middleware-host-header": "3.804.0",
|
||||
"@aws-sdk/middleware-logger": "3.804.0",
|
||||
"@aws-sdk/middleware-recursion-detection": "3.804.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.816.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.812.0",
|
||||
"@aws-sdk/region-config-resolver": "3.808.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@aws-sdk/util-endpoints": "3.808.0",
|
||||
"@aws-sdk/util-user-agent-browser": "3.804.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.816.0",
|
||||
"@aws-sdk/util-user-agent-node": "3.812.0",
|
||||
"@smithy/config-resolver": "^4.1.2",
|
||||
"@smithy/core": "^3.3.3",
|
||||
"@smithy/fetch-http-handler": "^5.0.2",
|
||||
@@ -1053,12 +1053,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/s3-request-presigner": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.817.0.tgz",
|
||||
"integrity": "sha512-FMV0YefefGwPqIbGcHdkkHaiVWKIZoI0wOhYhYDZI129aUD5+CEOtTi7KFp1iJjAK+Cx9bW5tAYc+e9shaWEyQ==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.812.0.tgz",
|
||||
"integrity": "sha512-OpyANELjcD2oknkd3/qWanaRaZDx4SSV6NwYuWIk+fuxDZ+KxZZrrfue1X7OAdaP2TdSapbs7xLisxtTuptWYg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/signature-v4-multi-region": "3.816.0",
|
||||
"@aws-sdk/signature-v4-multi-region": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@aws-sdk/util-format-url": "3.804.0",
|
||||
"@smithy/middleware-endpoint": "^4.1.6",
|
||||
@@ -1072,12 +1072,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/signature-v4-multi-region": {
|
||||
"version": "3.816.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.816.0.tgz",
|
||||
"integrity": "sha512-idcr9NW86sSIXASSej3423Selu6fxlhhJJtMgpAqoCH/HJh1eQrONJwNKuI9huiruPE8+02pwxuePvLW46X2mw==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.812.0.tgz",
|
||||
"integrity": "sha512-JTpk3ZHf7TXYbicKfOKi+VrsBTqcAszg9QR9fQmT9aCxPp39gsF3WsXq7NjepwZ5So11ixGIsPE/jtMym399QQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/middleware-sdk-s3": "3.816.0",
|
||||
"@aws-sdk/middleware-sdk-s3": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/protocol-http": "^5.1.0",
|
||||
"@smithy/signature-v4": "^5.1.0",
|
||||
@@ -1089,13 +1089,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/token-providers": {
|
||||
"version": "3.817.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.817.0.tgz",
|
||||
"integrity": "sha512-CYN4/UO0VaqyHf46ogZzNrVX7jI3/CfiuktwKlwtpKA6hjf2+ivfgHSKzPpgPBcSEfiibA/26EeLuMnB6cpSrQ==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.812.0.tgz",
|
||||
"integrity": "sha512-dbVBaKxrxE708ub5uH3w+cmKIeRQas+2Xf6rpckhohYY+IiflGOdK6aLrp3T6dOQgr/FJ37iQtcYNonAG+yVBQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/core": "3.816.0",
|
||||
"@aws-sdk/nested-clients": "3.817.0",
|
||||
"@aws-sdk/nested-clients": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/property-provider": "^4.0.2",
|
||||
"@smithy/shared-ini-file-loader": "^4.0.2",
|
||||
@@ -1186,12 +1185,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/util-user-agent-node": {
|
||||
"version": "3.816.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.816.0.tgz",
|
||||
"integrity": "sha512-Q6dxmuj4hL7pudhrneWEQ7yVHIQRBFr0wqKLF1opwOi1cIePuoEbPyJ2jkel6PDEv1YMfvsAKaRshp6eNA8VHg==",
|
||||
"version": "3.812.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.812.0.tgz",
|
||||
"integrity": "sha512-8pt+OkHhS2U0LDwnzwRnFxyKn8sjSe752OIZQCNv263odud8jQu9pYO2pKqb2kRBk9h9szynjZBDLXfnvSQ7Bg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/middleware-user-agent": "3.816.0",
|
||||
"@aws-sdk/middleware-user-agent": "3.812.0",
|
||||
"@aws-sdk/types": "3.804.0",
|
||||
"@smithy/node-config-provider": "^4.1.1",
|
||||
"@smithy/types": "^4.2.0",
|
||||
@@ -5474,9 +5473,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dd-trace": {
|
||||
"version": "5.53.0",
|
||||
"resolved": "https://registry.npmjs.org/dd-trace/-/dd-trace-5.53.0.tgz",
|
||||
"integrity": "sha512-ayraB+H05yAag5Ia70YwNkkAS4q0O/Bx1suijTUaYBXirTVlfK9CDSpZRf0Rcjk2uRqf8ANNNsws1fesP4cRmQ==",
|
||||
"version": "5.52.0",
|
||||
"resolved": "https://registry.npmjs.org/dd-trace/-/dd-trace-5.52.0.tgz",
|
||||
"integrity": "sha512-ZF+OWLMcgVUWJEAIYIl76LocgnbbkPJ6WgJCG1fhLk4UCsUvoHRvBx9qlexbytL0jkktk1pvzODcjL0wyxLAOQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "(Apache-2.0 OR BSD-3-Clause)",
|
||||
"dependencies": {
|
||||
@@ -9035,9 +9034,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/moment-timezone": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.6.0.tgz",
|
||||
"integrity": "sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q==",
|
||||
"version": "0.5.48",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
|
||||
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"moment": "^2.29.4"
|
||||
|
||||
20
package.json
20
package.json
@@ -16,14 +16,14 @@
|
||||
"job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.817.0",
|
||||
"@aws-sdk/client-elasticache": "^3.817.0",
|
||||
"@aws-sdk/client-s3": "^3.817.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.817.0",
|
||||
"@aws-sdk/client-ses": "^3.817.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.817.0",
|
||||
"@aws-sdk/lib-storage": "^3.817.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.817.0",
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.812.0",
|
||||
"@aws-sdk/client-elasticache": "^3.812.0",
|
||||
"@aws-sdk/client-s3": "^3.812.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.812.0",
|
||||
"@aws-sdk/client-ses": "^3.812.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.812.0",
|
||||
"@aws-sdk/lib-storage": "^3.812.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.812.0",
|
||||
"@opensearch-project/opensearch": "^2.13.0",
|
||||
"@socket.io/admin-ui": "^0.5.1",
|
||||
"@socket.io/redis-adapter": "^8.3.0",
|
||||
@@ -38,7 +38,7 @@
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"crisp-status-reporter": "^1.2.2",
|
||||
"dd-trace": "^5.53.0",
|
||||
"dd-trace": "^5.52.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.1",
|
||||
@@ -52,7 +52,7 @@
|
||||
"juice": "^11.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.6.0",
|
||||
"moment-timezone": "^0.5.48",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-persist": "^4.0.4",
|
||||
"nodemailer": "^6.10.0",
|
||||
|
||||
@@ -282,7 +282,6 @@ const applySocketIO = async ({ server, app }) => {
|
||||
logger.log("Redis connections closed.", "INFO", "redis", "api");
|
||||
});
|
||||
|
||||
// IO Redis
|
||||
const ioRedis = new Server(server, {
|
||||
path: "/wss",
|
||||
adapter: createAdapter(pubClient, subClient),
|
||||
|
||||
@@ -478,7 +478,7 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
|
||||
|
||||
exports.InsertJob = InsertJob;
|
||||
|
||||
async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
|
||||
async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid, jobid) {
|
||||
const items = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(qbo_realmId, "query", `select * From Item where active=true maxresults 1000`),
|
||||
method: "POST",
|
||||
@@ -492,6 +492,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
|
||||
name: "QueryItems",
|
||||
status: items.response?.status,
|
||||
bodyshopid,
|
||||
jobid: jobid,
|
||||
email: req.user.email
|
||||
})
|
||||
setNewRefreshToken(req.user.email, items);
|
||||
@@ -508,6 +509,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
|
||||
name: "QueryTaxCodes",
|
||||
status: taxCodes.response?.status,
|
||||
bodyshopid,
|
||||
jobid: jobid,
|
||||
email: req.user.email
|
||||
})
|
||||
const classes = await oauthClient.makeApiCall({
|
||||
@@ -523,6 +525,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
|
||||
name: "QueryClasses",
|
||||
status: classes.response?.status,
|
||||
bodyshopid,
|
||||
jobid: jobid,
|
||||
email: req.user.email
|
||||
})
|
||||
const taxCodeMapping = {};
|
||||
@@ -559,7 +562,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, bodyshopid) {
|
||||
}
|
||||
|
||||
async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, parentTierRef) {
|
||||
const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req, job.shopid);
|
||||
const { items, taxCodes, classes } = await QueryMetaData(oauthClient, qbo_realmId, req, job.shopid, job.id);
|
||||
const InvoiceLineAdd = CreateInvoiceLines({
|
||||
bodyshop,
|
||||
jobs_by_pk: job,
|
||||
@@ -653,7 +656,7 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren
|
||||
platform: "QBO",
|
||||
method: "POST",
|
||||
name: "InsertInvoice",
|
||||
status: result.status,
|
||||
status: result.response?.status,
|
||||
bodyshopid: job.shopid,
|
||||
jobid: job.id,
|
||||
email: req.user.email
|
||||
@@ -778,7 +781,7 @@ async function InsertInvoiceMultiPayerInvoice(
|
||||
platform: "QBO",
|
||||
method: "POST",
|
||||
name: "InsertInvoice",
|
||||
status: result.response.status,
|
||||
status: result.response?.status,
|
||||
bodyshopid: job.shopid,
|
||||
jobid: job.id,
|
||||
email: req.user.email
|
||||
|
||||
@@ -2980,59 +2980,4 @@ exports.INSERT_INTEGRATION_LOG = `
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
exports.INSERT_PHONE_NUMBER_OPT_OUT = `
|
||||
mutation INSERT_PHONE_NUMBER_OPT_OUT($optOutInput: [phone_number_opt_out_insert_input!]!) {
|
||||
insert_phone_number_opt_out(objects: $optOutInput, on_conflict: { constraint: phone_number_consent_bodyshopid_phone_number_key, update_columns: [updated_at] }) {
|
||||
affected_rows
|
||||
returning {
|
||||
id
|
||||
bodyshopid
|
||||
phone_number
|
||||
created_at
|
||||
updated_at
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Query to check if a phone number is opted out
|
||||
exports.CHECK_PHONE_NUMBER_OPT_OUT = `
|
||||
query CHECK_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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Query to check if a phone number is opted out
|
||||
exports.CHECK_PHONE_NUMBER_OPT_OUT = `
|
||||
query CHECK_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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Mutation to delete a phone number opt-out record
|
||||
exports.DELETE_PHONE_NUMBER_OPT_OUT = `
|
||||
mutation DELETE_PHONE_NUMBER_OPT_OUT($bodyshopid: uuid!, $phone_number: String!) {
|
||||
delete_phone_number_opt_out(where: { bodyshopid: { _eq: $bodyshopid }, phone_number: { _eq: $phone_number } }) {
|
||||
affected_rows
|
||||
returning {
|
||||
id
|
||||
bodyshopid
|
||||
phone_number
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
`;
|
||||
@@ -3,25 +3,12 @@ const {
|
||||
FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID,
|
||||
UNARCHIVE_CONVERSATION,
|
||||
CREATE_CONVERSATION,
|
||||
INSERT_MESSAGE,
|
||||
CHECK_PHONE_NUMBER_OPT_OUT,
|
||||
DELETE_PHONE_NUMBER_OPT_OUT,
|
||||
INSERT_PHONE_NUMBER_OPT_OUT
|
||||
INSERT_MESSAGE
|
||||
} = require("../graphql-client/queries");
|
||||
const { phone } = require("phone");
|
||||
const { admin } = require("../firebase/firebase-handler");
|
||||
const InstanceManager = require("../utils/instanceMgr").default;
|
||||
|
||||
// Note: When we handle different languages, we might need to adjust these keywords accordingly.
|
||||
const optInKeywords = ["START", "YES", "UNSTOP"];
|
||||
const optOutKeywords = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT", "REVOKE", "OPTOUT"];
|
||||
|
||||
// System Message text, will also need to be localized if we support multiple languages
|
||||
const systemMessageOptions = {
|
||||
optIn: "Customer has opted-in",
|
||||
optOut: "Customer has opted-out"
|
||||
};
|
||||
|
||||
/**
|
||||
* Receive SMS messages from Twilio and process them
|
||||
* @param req
|
||||
@@ -64,16 +51,22 @@ const receive = async (req, res) => {
|
||||
}
|
||||
|
||||
const bodyshop = response.bodyshops[0];
|
||||
const normalizedPhone = phone(req.body.From).phoneNumber.replace(/^\+1/, ""); // Normalize phone number (remove +1 for CA numbers)
|
||||
const messageText = (req.body.Body || "").trim().toUpperCase();
|
||||
|
||||
// Step 2: Process conversation
|
||||
// Step 4: Process conversation
|
||||
const sortedConversations = bodyshop.conversations.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
|
||||
const existingConversation = sortedConversations.length
|
||||
? sortedConversations[sortedConversations.length - 1]
|
||||
: null;
|
||||
|
||||
let conversationid;
|
||||
let newMessage = {
|
||||
msid: req.body.SmsMessageSid,
|
||||
text: req.body.Body,
|
||||
image: !!req.body.MediaUrl0,
|
||||
image_path: generateMediaArray(req.body, logger),
|
||||
isoutbound: false,
|
||||
userid: null
|
||||
};
|
||||
|
||||
if (existingConversation) {
|
||||
conversationid = existingConversation.id;
|
||||
@@ -95,134 +88,9 @@ const receive = async (req, res) => {
|
||||
conversationid = createdConversation.id;
|
||||
}
|
||||
|
||||
// Step 3: Handle opt-in or opt-out keywords
|
||||
let systemMessageText = "";
|
||||
let socketEventType = "";
|
||||
|
||||
if (optInKeywords.includes(messageText) || optOutKeywords.includes(messageText)) {
|
||||
// Check if the phone number is in phone_number_opt_out
|
||||
const optOutCheck = await client.request(CHECK_PHONE_NUMBER_OPT_OUT, {
|
||||
bodyshopid: bodyshop.id,
|
||||
phone_number: normalizedPhone
|
||||
});
|
||||
|
||||
// Opt In
|
||||
if (optInKeywords.includes(messageText)) {
|
||||
// Handle opt-in
|
||||
if (optOutCheck.phone_number_opt_out.length > 0) {
|
||||
// Phone number is opted out; delete the record
|
||||
const deleteResponse = await client.request(DELETE_PHONE_NUMBER_OPT_OUT, {
|
||||
bodyshopid: bodyshop.id,
|
||||
phone_number: normalizedPhone
|
||||
});
|
||||
|
||||
logger.log("sms-opt-in-success", "INFO", "api", null, {
|
||||
msid: req.body.SmsMessageSid,
|
||||
bodyshopid: bodyshop.id,
|
||||
phone_number: normalizedPhone,
|
||||
affected_rows: deleteResponse.delete_phone_number_opt_out.affected_rows
|
||||
});
|
||||
|
||||
systemMessageText = systemMessageOptions.optIn;
|
||||
socketEventType = "phone-number-opted-in";
|
||||
}
|
||||
}
|
||||
// Opt Out
|
||||
else if (optOutKeywords.includes(messageText)) {
|
||||
// Handle opt-out
|
||||
if (optOutCheck.phone_number_opt_out.length === 0) {
|
||||
// Phone number is not opted out; insert a new record
|
||||
const now = new Date().toISOString();
|
||||
const optOutInput = {
|
||||
bodyshopid: bodyshop.id,
|
||||
phone_number: normalizedPhone,
|
||||
created_at: now,
|
||||
updated_at: now
|
||||
};
|
||||
|
||||
const insertResponse = await client.request(INSERT_PHONE_NUMBER_OPT_OUT, {
|
||||
optOutInput: [optOutInput]
|
||||
});
|
||||
|
||||
logger.log("sms-opt-out-success", "INFO", "api", null, {
|
||||
msid: req.body.SmsMessageSid,
|
||||
bodyshopid: bodyshop.id,
|
||||
phone_number: normalizedPhone,
|
||||
affected_rows: insertResponse.insert_phone_number_opt_out.affected_rows
|
||||
});
|
||||
|
||||
systemMessageText = systemMessageOptions.optOut;
|
||||
socketEventType = "phone-number-opted-out";
|
||||
}
|
||||
}
|
||||
|
||||
// Insert system message if an opt-in or opt-out action was taken
|
||||
if (systemMessageText) {
|
||||
const systemMessage = {
|
||||
msid: `SYS_${req.body.SmsMessageSid}_${Date.now()}`, // Unique ID for system message
|
||||
text: systemMessageText,
|
||||
conversationid,
|
||||
isoutbound: false,
|
||||
userid: null,
|
||||
image: false,
|
||||
image_path: null,
|
||||
is_system: true
|
||||
};
|
||||
|
||||
const systemMessageResponse = await client.request(INSERT_MESSAGE, {
|
||||
msg: systemMessage,
|
||||
conversationid
|
||||
});
|
||||
|
||||
const insertedSystemMessage = systemMessageResponse.insert_messages.returning[0];
|
||||
|
||||
// Emit WebSocket events for system message
|
||||
const broadcastRoom = getBodyshopRoom(bodyshop.id);
|
||||
const conversationRoom = getBodyshopConversationRoom({
|
||||
bodyshopId: bodyshop.id,
|
||||
conversationId: conversationid
|
||||
});
|
||||
|
||||
const systemPayload = {
|
||||
isoutbound: false,
|
||||
conversationId: conversationid,
|
||||
updated_at: insertedSystemMessage.updated_at,
|
||||
msid: insertedSystemMessage.msid,
|
||||
existingConversation: !!existingConversation,
|
||||
newConversation: !existingConversation ? insertedSystemMessage.conversation : null
|
||||
};
|
||||
|
||||
ioRedis.to(broadcastRoom).emit("new-message-summary", {
|
||||
...systemPayload,
|
||||
summary: true
|
||||
});
|
||||
|
||||
ioRedis.to(conversationRoom).emit("new-message-detailed", {
|
||||
newMessage: insertedSystemMessage,
|
||||
...systemPayload,
|
||||
summary: false
|
||||
});
|
||||
|
||||
// Emit opt-in or opt-out event
|
||||
ioRedis.to(broadcastRoom).emit(socketEventType, {
|
||||
bodyshopid: bodyshop.id,
|
||||
phone_number: normalizedPhone
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Insert the original message
|
||||
const newMessage = {
|
||||
msid: req.body.SmsMessageSid,
|
||||
text: req.body.Body,
|
||||
image: !!req.body.MediaUrl0,
|
||||
image_path: generateMediaArray(req.body, logger),
|
||||
isoutbound: false,
|
||||
userid: null,
|
||||
conversationid,
|
||||
is_system: false
|
||||
};
|
||||
newMessage.conversationid = conversationid;
|
||||
|
||||
// Step 5: Insert the message
|
||||
const insertresp = await client.request(INSERT_MESSAGE, {
|
||||
msg: newMessage,
|
||||
conversationid
|
||||
@@ -235,7 +103,7 @@ const receive = async (req, res) => {
|
||||
throw new Error("Conversation data is missing from the response.");
|
||||
}
|
||||
|
||||
// Step 5: Notify clients for original message
|
||||
// Step 6: Notify clients
|
||||
const conversationRoom = getBodyshopConversationRoom({
|
||||
bodyshopId: conversation.bodyshop.id,
|
||||
conversationId: conversation.id
|
||||
@@ -245,7 +113,7 @@ const receive = async (req, res) => {
|
||||
isoutbound: false,
|
||||
conversationId: conversation.id,
|
||||
updated_at: message.updated_at,
|
||||
msid: message.msid
|
||||
msid: message.sid
|
||||
};
|
||||
|
||||
const broadcastRoom = getBodyshopRoom(conversation.bodyshop.id);
|
||||
@@ -265,7 +133,7 @@ const receive = async (req, res) => {
|
||||
summary: false
|
||||
});
|
||||
|
||||
// Step 6: Send FCM notification
|
||||
// Step 7: Send FCM notification
|
||||
const fcmresp = await admin.messaging().send({
|
||||
topic: `${message.conversation.bodyshop.imexshopid}-messaging`,
|
||||
notification: {
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const {
|
||||
UPDATE_MESSAGE_STATUS,
|
||||
MARK_MESSAGES_AS_READ,
|
||||
INSERT_PHONE_NUMBER_OPT_OUT,
|
||||
FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID
|
||||
} = require("../graphql-client/queries");
|
||||
const { UPDATE_MESSAGE_STATUS, MARK_MESSAGES_AS_READ } = require("../graphql-client/queries");
|
||||
const logger = require("../utils/logger");
|
||||
const { phone } = require("phone");
|
||||
|
||||
/**
|
||||
* Handle the status of an SMS message
|
||||
@@ -15,7 +9,7 @@ const { phone } = require("phone");
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
const status = async (req, res) => {
|
||||
const { SmsSid, SmsStatus, ErrorCode, To, MessagingServiceSid } = req.body;
|
||||
const { SmsSid, SmsStatus } = req.body;
|
||||
const {
|
||||
ioRedis,
|
||||
ioHelpers: { getBodyshopRoom, getBodyshopConversationRoom }
|
||||
@@ -27,76 +21,18 @@ const status = async (req, res) => {
|
||||
return res.status(200).json({ message: "Status 'queued' disregarded." });
|
||||
}
|
||||
|
||||
// Handle ErrorCode 21610 (Attempt to send to unsubscribed recipient) first
|
||||
if (ErrorCode === "21610" && To && MessagingServiceSid) {
|
||||
try {
|
||||
// Step 1: Find the bodyshop by MessagingServiceSid
|
||||
const bodyshopResponse = await client.request(FIND_BODYSHOP_BY_MESSAGING_SERVICE_SID, {
|
||||
mssid: MessagingServiceSid,
|
||||
phone: phone(To).phoneNumber // Pass the normalized phone number as required
|
||||
});
|
||||
|
||||
const bodyshop = bodyshopResponse.bodyshops[0];
|
||||
if (!bodyshop) {
|
||||
logger.log("sms-opt-out-error", "ERROR", "api", null, {
|
||||
msid: SmsSid,
|
||||
messagingServiceSid: MessagingServiceSid,
|
||||
to: To,
|
||||
error: "No matching bodyshop found"
|
||||
});
|
||||
} else {
|
||||
// Step 2: Insert into phone_number_opt_out table
|
||||
const now = new Date().toISOString();
|
||||
const optOutInput = {
|
||||
bodyshopid: bodyshop.id,
|
||||
phone_number: phone(To).phoneNumber.replace(/^\+1/, ""), // Normalize phone number (remove +1 for CA numbers)
|
||||
created_at: now,
|
||||
updated_at: now
|
||||
};
|
||||
|
||||
const optOutResponse = await client.request(INSERT_PHONE_NUMBER_OPT_OUT, {
|
||||
optOutInput: [optOutInput]
|
||||
});
|
||||
|
||||
logger.log("sms-opt-out-success", "INFO", null, null, {
|
||||
msid: SmsSid,
|
||||
bodyshopid: bodyshop.id,
|
||||
phone_number: optOutInput.phone_number,
|
||||
affected_rows: optOutResponse.insert_phone_number_opt_out.affected_rows
|
||||
});
|
||||
|
||||
// Store bodyshopid for potential use in WebSocket notification
|
||||
const broadcastRoom = getBodyshopRoom(bodyshop.id);
|
||||
ioRedis.to(broadcastRoom).emit("phone-number-opted-out", {
|
||||
bodyshopid: bodyshop.id,
|
||||
phone_number: optOutInput.phone_number
|
||||
// Note: conversationId is not included yet; will be set after message lookup
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log("sms-opt-out-error", "ERROR", "api", null, {
|
||||
msid: SmsSid,
|
||||
messagingServiceSid: MessagingServiceSid,
|
||||
to: To,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
// Continue processing to update message status
|
||||
}
|
||||
}
|
||||
|
||||
// Update message status in the database
|
||||
const response = await client.request(UPDATE_MESSAGE_STATUS, {
|
||||
msid: SmsSid,
|
||||
fields: { status: SmsStatus }
|
||||
});
|
||||
|
||||
const message = response.update_messages?.returning?.[0];
|
||||
const message = response.update_messages.returning[0];
|
||||
|
||||
if (message) {
|
||||
logger.log("sms-status-update", "DEBUG", "api", null, {
|
||||
msid: SmsSid,
|
||||
status: SmsStatus
|
||||
fields: { status: SmsStatus }
|
||||
});
|
||||
|
||||
// Emit WebSocket event to notify the change in message status
|
||||
@@ -111,20 +47,20 @@ const status = async (req, res) => {
|
||||
type: "status-changed"
|
||||
});
|
||||
} else {
|
||||
logger.log("sms-status-update-warning", "WARN", null, null, {
|
||||
logger.log("sms-status-update-warning", "WARN", "api", null, {
|
||||
msid: SmsSid,
|
||||
status: SmsStatus,
|
||||
warning: "No message found in database for update"
|
||||
fields: { status: SmsStatus },
|
||||
warning: "No message returned from the database update."
|
||||
});
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
logger.log("sms-status-update-error", "ERROR", "api", null, {
|
||||
msid: SmsSid,
|
||||
status: SmsStatus,
|
||||
error: err.message,
|
||||
stack: err.stack
|
||||
fields: { status: SmsStatus },
|
||||
stack: error.stack,
|
||||
message: error.message
|
||||
});
|
||||
res.status(500).json({ error: "Failed to update message status." });
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ const createLogger = () => {
|
||||
jobid,
|
||||
paymentid,
|
||||
billid,
|
||||
status: status.toString() ?? "0",
|
||||
status: status?.toString() ?? "0",
|
||||
bodyshopid,
|
||||
email
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user