Compare commits
435 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac6856b136 | ||
|
|
ddd3b3d056 | ||
|
|
2660466db1 | ||
|
|
fe67efe47c | ||
|
|
69a35772e5 | ||
|
|
38932f4bf9 | ||
|
|
3fcb36a28e | ||
|
|
fe78f5c7ff | ||
|
|
683846c3b0 | ||
|
|
2cc0b247b6 | ||
|
|
31579354d4 | ||
|
|
0e7531dc54 | ||
|
|
268b57c38a | ||
|
|
2b8b8b8073 | ||
|
|
808eeb91e9 | ||
|
|
23dd8fc9de | ||
|
|
f499859078 | ||
|
|
84d9e3251a | ||
|
|
bd7db4dd02 | ||
|
|
e9804b736b | ||
|
|
a14874f116 | ||
|
|
ac9fac458c | ||
|
|
8f9db15852 | ||
|
|
61b3d3c18c | ||
|
|
e6f08d3b1c | ||
|
|
d5cf0f8371 | ||
|
|
52e230fc54 | ||
|
|
4011237c22 | ||
|
|
c24bfbf655 | ||
|
|
08fe8c3c70 | ||
|
|
771a239773 | ||
|
|
82195a0584 | ||
|
|
838c24b3f1 | ||
|
|
13cb68b0af | ||
|
|
7c84b08707 | ||
|
|
3165957e95 | ||
|
|
d9f59fcad4 | ||
|
|
edaeb5d77a | ||
|
|
5365d95d6f | ||
|
|
5cfefd5afd | ||
|
|
bbccdb0650 | ||
|
|
f3535c01af | ||
|
|
7f8c82b300 | ||
|
|
609ac2bd33 | ||
|
|
0883274320 | ||
|
|
fa33b88632 | ||
|
|
bec32c1d70 | ||
|
|
eb18130e51 | ||
|
|
0fbf63dec8 | ||
|
|
f817902d5c | ||
|
|
7a383aaec9 | ||
|
|
e5c0ace6cb | ||
|
|
814447373a | ||
|
|
d766a468c3 | ||
|
|
0ede2d0649 | ||
|
|
54089c2ab3 | ||
|
|
73e3d71cf1 | ||
|
|
f071a5cc9e | ||
|
|
67002b8443 | ||
|
|
3f83c6afa7 | ||
|
|
b7f57e91aa | ||
|
|
b8465c0cc7 | ||
|
|
6bae0b8406 | ||
|
|
553c154e46 | ||
|
|
35cbb921d2 | ||
|
|
6b926401d0 | ||
|
|
5b400dce4f | ||
|
|
914946c264 | ||
|
|
2939b5795b | ||
|
|
531f968ce6 | ||
|
|
24cc3fa6a4 | ||
|
|
a6f17e7db5 | ||
|
|
29c904feea | ||
|
|
0743048e75 | ||
|
|
e2f1758378 | ||
|
|
b6dc7a4d92 | ||
|
|
130745d7e7 | ||
|
|
a722ab9758 | ||
|
|
41c9c0be49 | ||
|
|
52f62126e1 | ||
|
|
cfdfb8110b | ||
|
|
eb04a8b6c5 | ||
|
|
141b05f558 | ||
|
|
e9ea36fdad | ||
|
|
e990781ff7 | ||
|
|
e24237e010 | ||
|
|
f00f5f2e4a | ||
|
|
f38b550e75 | ||
|
|
fabe1508ac | ||
|
|
5f3c880d0b | ||
|
|
86f0c02c82 | ||
|
|
2004eb840f | ||
|
|
2f0190e190 | ||
|
|
e5a48531a0 | ||
|
|
9860447e42 | ||
|
|
364813193f | ||
|
|
2c8f3a173e | ||
|
|
1577921334 | ||
|
|
a934249c02 | ||
|
|
6486de3c61 | ||
|
|
ed6a8b210d | ||
|
|
96e38d509f | ||
|
|
42e072e8ff | ||
|
|
6942d6e4f9 | ||
|
|
43d5a77d60 | ||
|
|
a74ce063ec | ||
|
|
9c45b49ab9 | ||
|
|
d690790dfe | ||
|
|
70fa638c37 | ||
|
|
77cacdec91 | ||
|
|
33fb60ca1a | ||
|
|
f6d6b548be | ||
|
|
c0a215d20d | ||
|
|
8c9ef375be | ||
|
|
5342377ca9 | ||
|
|
8295cb111a | ||
|
|
cd4754069b | ||
|
|
951d214d49 | ||
|
|
c586d0283b | ||
|
|
203cc1ebdf | ||
|
|
db09f33e5c | ||
|
|
1941034dcb | ||
|
|
6bd2828176 | ||
|
|
6f19c1dd3f | ||
|
|
b428a1078c | ||
|
|
cc232eac93 | ||
|
|
5e90504e56 | ||
|
|
73e103f2df | ||
|
|
b09b8b4f34 | ||
|
|
9dd34c9f6c | ||
|
|
cf60f7cd03 | ||
|
|
637e95c351 | ||
|
|
7af7f3c4e7 | ||
|
|
0cadf007b5 | ||
|
|
1394176218 | ||
|
|
284d25eeb9 | ||
|
|
60258a0f5d | ||
|
|
7873405a30 | ||
|
|
c38d7d9aea | ||
|
|
7639655911 | ||
|
|
4fb1871044 | ||
|
|
e5dd1edf13 | ||
|
|
542c95c395 | ||
|
|
a3122a59b1 | ||
|
|
81d642fcd3 | ||
|
|
960a2ccd30 | ||
|
|
2ab6093bd8 | ||
|
|
7ed7b6117f | ||
|
|
7158676562 | ||
|
|
67a8c13bad | ||
|
|
766b4b950a | ||
|
|
88ba8ab929 | ||
|
|
0d570d0323 | ||
|
|
898b97151f | ||
|
|
de6bb3d634 | ||
|
|
2b40793c77 | ||
|
|
c4649b2fc6 | ||
|
|
4d475e25fa | ||
|
|
00d2d3012d | ||
|
|
4e5aba59d7 | ||
|
|
09f96f0b68 | ||
|
|
866f5f72eb | ||
|
|
f0c166907b | ||
|
|
c06b4e8af5 | ||
|
|
bebe3ef633 | ||
|
|
50d2e912ed | ||
|
|
a7e21b0505 | ||
|
|
3b481afa9e | ||
|
|
75de177b7b | ||
|
|
ec6c0279de | ||
|
|
c9572d2db5 | ||
|
|
93e9e20f6f | ||
|
|
4e8ea736c5 | ||
|
|
8f00dbfc17 | ||
|
|
55d242d40d | ||
|
|
4f99ae40d3 | ||
|
|
05c049c9af | ||
|
|
d94b573ae6 | ||
|
|
790ab0447f | ||
|
|
84795b2048 | ||
|
|
567002236d | ||
|
|
0ed41de956 | ||
|
|
8a2dfae487 | ||
|
|
3737fe457f | ||
|
|
c5978d4c21 | ||
|
|
bb4e8eb5bd | ||
|
|
27a07e8d5d | ||
|
|
e52fe93e14 | ||
|
|
e2618eee83 | ||
|
|
dd20871707 | ||
|
|
66c51a4be5 | ||
|
|
d5afcaeaab | ||
|
|
d0370d3e60 | ||
|
|
c332ec11b7 | ||
|
|
cf31290f05 | ||
|
|
203dc28720 | ||
|
|
dbbab910b6 | ||
|
|
abf01b4966 | ||
|
|
952feeb685 | ||
|
|
bb2b67cece | ||
|
|
a965f9edf5 | ||
|
|
f02ca05eba | ||
|
|
a182ea0869 | ||
|
|
7bc2d41a68 | ||
|
|
e4fb8b61b0 | ||
|
|
1cc33a67e6 | ||
|
|
5277e90946 | ||
|
|
0a16a0fcbc | ||
|
|
15ea4e6afa | ||
|
|
295f1d1cb3 | ||
|
|
5b3b6a409c | ||
|
|
d92bab113e | ||
|
|
93c6e2b601 | ||
|
|
19a90571f6 | ||
|
|
37ceddf54d | ||
|
|
736e9cedfa | ||
|
|
c433103e1b | ||
|
|
2892fdbb58 | ||
|
|
c45f38e47b | ||
|
|
6f4d21cac9 | ||
|
|
6b5ad3dafa | ||
|
|
54a58c9fbc | ||
|
|
1934ae0758 | ||
|
|
0ac9bbd97c | ||
|
|
dfa457e3c6 | ||
|
|
e8dec042bd | ||
|
|
d65e7deacc | ||
|
|
953e70efef | ||
|
|
a6bae390e5 | ||
|
|
139ee46dc0 | ||
|
|
cf9d8d649d | ||
|
|
a25051c4c2 | ||
|
|
7a1e7bd4f9 | ||
|
|
254fb4f77f | ||
|
|
53a559b126 | ||
|
|
d5c3152631 | ||
|
|
66c425bf96 | ||
|
|
ffad0dfbf7 | ||
|
|
17285fc029 | ||
|
|
401e3cff73 | ||
|
|
865680e019 | ||
|
|
9f97ca0336 | ||
|
|
5df38f8612 | ||
|
|
63c5719420 | ||
|
|
d6c80f1420 | ||
|
|
fade927c9e | ||
|
|
9f472ce1d0 | ||
|
|
47a56e32b9 | ||
|
|
f13f79acb6 | ||
|
|
bfa9fddb9e | ||
|
|
28abd9707e | ||
|
|
5f621e1ae0 | ||
|
|
624414799e | ||
|
|
72091e9eae | ||
|
|
9cfacdd025 | ||
|
|
d5c63b798a | ||
|
|
655e516246 | ||
|
|
7b12f0a3b9 | ||
|
|
e0b937474d | ||
|
|
5c4267f3ef | ||
|
|
4dcfb382a9 | ||
|
|
cf181dfd0a | ||
|
|
1127864ba9 | ||
|
|
79e379b61a | ||
|
|
e79e512291 | ||
|
|
f0064abfbe | ||
|
|
4a30a5bc64 | ||
|
|
32bdea559e | ||
|
|
d4215b7aee | ||
|
|
2494399993 | ||
|
|
34f62a8858 | ||
|
|
9e5689b06f | ||
|
|
931fa4b82b | ||
|
|
5d69d37db2 | ||
|
|
9ab2fdc868 | ||
|
|
fbd6766dcd | ||
|
|
9ace531edb | ||
|
|
2e3944099b | ||
|
|
9b53bd9b40 | ||
|
|
443ed717cb | ||
|
|
9845c1cea5 | ||
|
|
2061a49e0e | ||
|
|
f8a3d0f854 | ||
|
|
91cc12873e | ||
|
|
c71026f22a | ||
|
|
bd2720f534 | ||
|
|
8bc6bea4b2 | ||
|
|
23901c0cc1 | ||
|
|
b99a212d75 | ||
|
|
0891c7d4b3 | ||
|
|
a4963922da | ||
|
|
3ae41b7016 | ||
|
|
9c59fd4c00 | ||
|
|
a9f959cced | ||
|
|
414897bba0 | ||
|
|
94b154a4ac | ||
|
|
5fa58e5013 | ||
|
|
6496f6c414 | ||
|
|
3a0a3c9fb9 | ||
|
|
7467a31d76 | ||
|
|
894f6bf6d2 | ||
|
|
744dfa8163 | ||
|
|
2293119518 | ||
|
|
bd529a0dfa | ||
|
|
646c42b8c7 | ||
|
|
8de92403ee | ||
|
|
57ad89747f | ||
|
|
3ae8f38adb | ||
|
|
dc5ed1a39c | ||
|
|
f5ea8719ef | ||
|
|
aa6e6b8980 | ||
|
|
1dc80c068b | ||
|
|
bd0c4ceae2 | ||
|
|
30b58c6ea5 | ||
|
|
a55e9224f8 | ||
|
|
0c80abb3ca | ||
|
|
7137e611cd | ||
|
|
6f9d291d36 | ||
|
|
f2a2653eae | ||
|
|
73c25ab91f | ||
|
|
780449bac6 | ||
|
|
2509a1ecf3 | ||
|
|
16075f7ddd | ||
|
|
27d28e7ffc | ||
|
|
66b87e5c45 | ||
|
|
c1e1dff7d2 | ||
|
|
0e1ec83fcd | ||
|
|
01185b3073 | ||
|
|
49b0990c7b | ||
|
|
f76eb7abf5 | ||
|
|
25ea2a80a3 | ||
|
|
7d930045ef | ||
|
|
633d5668f0 | ||
|
|
cbb6c43ec3 | ||
|
|
09e1887609 | ||
|
|
4b83330db9 | ||
|
|
b0283f827e | ||
|
|
3c71902047 | ||
|
|
79bf30b299 | ||
|
|
dc3e9b7226 | ||
|
|
00cc47553b | ||
|
|
2505edede7 | ||
|
|
4b75504d9e | ||
|
|
8af6c8dd24 | ||
|
|
4c6344a8d7 | ||
|
|
92369fceba | ||
|
|
3c360130a3 | ||
|
|
13e4143eeb | ||
|
|
68c7b184d2 | ||
|
|
9b85d15ff1 | ||
|
|
e7cf49a2ec | ||
|
|
04b29b6970 | ||
|
|
f5bc79cba7 | ||
|
|
2ae18681cb | ||
|
|
fda763476a | ||
|
|
999cbd80f4 | ||
|
|
ad2a5fe95b | ||
|
|
d835021069 | ||
|
|
c4b303aee1 | ||
|
|
e2c5a4cba4 | ||
|
|
fd04125ed1 | ||
|
|
a0566e76ab | ||
|
|
87e8b2ce27 | ||
|
|
d52426f5f5 | ||
|
|
5e24404e82 | ||
|
|
64a280b111 | ||
|
|
cf393e8f9e | ||
|
|
909a21023a | ||
|
|
0402156b4d | ||
|
|
94bdc6c43f | ||
|
|
9466d36e69 | ||
|
|
412efb06e5 | ||
|
|
da7e637183 | ||
|
|
2e95fa25af | ||
|
|
f6c63bbd74 | ||
|
|
0a654082c2 | ||
|
|
2c20b731d2 | ||
|
|
8a22897cdd | ||
|
|
677da61b52 | ||
|
|
6513434bd7 | ||
|
|
fe2600029f | ||
|
|
c5b4efedfb | ||
|
|
310321d0ab | ||
|
|
7e884c42ea | ||
|
|
e279bf41a4 | ||
|
|
4a060ab51c | ||
|
|
62c1c77a18 | ||
|
|
db19ecb28c | ||
|
|
51748ce28d | ||
|
|
4bbfd8a9da | ||
|
|
d4d2db2cac | ||
|
|
23483144e1 | ||
|
|
67d5dcb062 | ||
|
|
901a49e571 | ||
|
|
49ae107fde | ||
|
|
0135281bcd | ||
|
|
99cf95daf0 | ||
|
|
8c1758ae49 | ||
|
|
2d764921ff | ||
|
|
858a11f8b4 | ||
|
|
4859239f55 | ||
|
|
5c64d7185e | ||
|
|
152479bc08 | ||
|
|
2c508cf1a1 | ||
|
|
16a91c772a | ||
|
|
5c47088b11 | ||
|
|
8e5dc4fa71 | ||
|
|
39c3729f6d | ||
|
|
e3d854e02b | ||
|
|
618acf2acf | ||
|
|
2cf2b70293 | ||
|
|
0541afceb8 | ||
|
|
28ed3f9936 | ||
|
|
6afa50332b | ||
|
|
8c8c68867d | ||
|
|
8ee52598e8 | ||
|
|
c822028174 | ||
|
|
36b82c6195 | ||
|
|
831802f5af | ||
|
|
7bd5190bf2 | ||
|
|
83860152a9 | ||
|
|
1e10493615 | ||
|
|
9d81c68a4d | ||
|
|
985d066978 | ||
|
|
6ad9e27d1d | ||
|
|
19ebdda5b3 | ||
|
|
4602dd1183 | ||
|
|
6d59e3994f | ||
|
|
f770b2f1b1 | ||
|
|
b014744940 | ||
|
|
714c90c25e | ||
|
|
9a3a971da6 | ||
|
|
96cba0aaab | ||
|
|
d4f718c44c | ||
|
|
3fe0e3a33c |
@@ -11,7 +11,6 @@ node_modules
|
||||
# Files to exclude
|
||||
.ebignore
|
||||
.editorconfig
|
||||
.eslintrc.json
|
||||
.gitignore
|
||||
.prettierrc.js
|
||||
Dockerfile
|
||||
@@ -19,6 +18,6 @@ README.MD
|
||||
bodyshop_translations.babel
|
||||
docker-compose.yml
|
||||
ecosystem.config.js
|
||||
|
||||
eslint.config.mjs
|
||||
# Optional: Exclude logs and temporary files
|
||||
*.log
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"Atomics": "readonly",
|
||||
"SharedArrayBuffer": "readonly"
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-console": "off"
|
||||
},
|
||||
"settings": {}
|
||||
}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -130,3 +130,5 @@ test-output.txt
|
||||
server/job/test/fixtures
|
||||
|
||||
.github
|
||||
_reference/ragmate/.ragmate.env
|
||||
docker_data
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
client_max_body_size 50M;
|
||||
client_body_buffer_size 5M;
|
||||
client_body_buffer_size 5M;
|
||||
10
_reference/ragmate/local-rag-compose.yml
Normal file
10
_reference/ragmate/local-rag-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
ragmate:
|
||||
image: ghcr.io/ragmate/ragmate:latest
|
||||
ports:
|
||||
- "11434:11434"
|
||||
env_file:
|
||||
- .ragmate.env
|
||||
volumes:
|
||||
- .:/project
|
||||
- ./docker_data/ragmate:/apps/cache
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,3 +14,6 @@ VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=IMEX
|
||||
TEST_USERNAME="test@imex.dev"
|
||||
TEST_PASSWORD="test123"
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||
@@ -16,3 +16,6 @@ VITE_APP_COUNTRY=USA
|
||||
VITE_APP_INSTANCE=ROME
|
||||
TEST_USERNAME="test@imex.dev"
|
||||
TEST_PASSWORD="test123"
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||
@@ -13,3 +13,6 @@ VITE_APP_AXIOS_BASE_API_URL=https://api.imex.online/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports.imex.online
|
||||
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||
VITE_APP_INSTANCE=IMEX
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||
@@ -13,3 +13,6 @@ VITE_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
||||
VITE_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||
VITE_APP_INSTANCE=ROME
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||
@@ -13,3 +13,6 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.imex.online
|
||||
VITE_APP_IS_TEST=true
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=IMEX
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||
@@ -13,3 +13,6 @@ VITE_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
||||
VITE_APP_IS_TEST=true
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=ROME
|
||||
VITE_PUBLIC_POSTHOG_KEY=phc_xtLmBIu0rjWwExY73Oj5DTH1bGbwq1G1Y8jnlTceien
|
||||
VITE_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com
|
||||
VITE_APP_AMP_URL=https://vp8k908qy2.execute-api.ca-central-1.amazonaws.com
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"react-app"
|
||||
],
|
||||
"rules": {
|
||||
"no-useless-rename": "off"
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import globals from "globals";
|
||||
import pluginJs from "@eslint/js";
|
||||
import pluginReact from "eslint-plugin-react";
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
|
||||
/** @type {import("eslint").Linter.Config[]} */
|
||||
export default [
|
||||
{ ignores: ["node_modules/**", "dist/**", "build/**", "dev-dist/**"] },
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs,jsx}"]
|
||||
},
|
||||
@@ -12,9 +12,13 @@ export default [
|
||||
pluginJs.configs.recommended,
|
||||
{
|
||||
...pluginReact.configs.flat.recommended,
|
||||
settings: {
|
||||
react: { version: "detect" }
|
||||
},
|
||||
rules: {
|
||||
...pluginReact.configs.flat.recommended.rules,
|
||||
"react/prop-types": 0
|
||||
"react/prop-types": 0,
|
||||
"react/no-children-prop": 0 // Disable react/no-children-prop rule
|
||||
}
|
||||
},
|
||||
pluginReact.configs.flat["jsx-runtime"]
|
||||
|
||||
6148
client/package-lock.json
generated
6148
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,57 +8,61 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@ant-design/pro-layout": "^7.22.4",
|
||||
"@apollo/client": "^3.13.6",
|
||||
"@emotion/is-prop-valid": "^1.3.1",
|
||||
"@amplitude/analytics-browser": "^2.23.5",
|
||||
"@ant-design/pro-layout": "^7.22.6",
|
||||
"@apollo/client": "^3.13.9",
|
||||
"@emotion/is-prop-valid": "^1.4.0",
|
||||
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
||||
"@firebase/analytics": "^0.10.13",
|
||||
"@firebase/app": "^0.12.1",
|
||||
"@firebase/auth": "^1.10.2",
|
||||
"@firebase/firestore": "^4.7.12",
|
||||
"@firebase/messaging": "^0.12.18",
|
||||
"@firebase/analytics": "^0.10.17",
|
||||
"@firebase/app": "^0.14.2",
|
||||
"@firebase/auth": "^1.10.8",
|
||||
"@firebase/firestore": "^4.9.1",
|
||||
"@firebase/messaging": "^0.12.22",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.8.1",
|
||||
"@sentry/cli": "^2.45.0",
|
||||
"@sentry/react": "^9.18.0",
|
||||
"@sentry/vite-plugin": "^3.4.0",
|
||||
"@splitsoftware/splitio-react": "^2.1.1",
|
||||
"@reduxjs/toolkit": "^2.9.0",
|
||||
"@sentry/cli": "^2.53.0",
|
||||
"@sentry/react": "^9.43.0",
|
||||
"@sentry/vite-plugin": "^4.3.0",
|
||||
"@splitsoftware/splitio-react": "^2.3.1",
|
||||
"@tanem/react-nprogress": "^5.0.53",
|
||||
"antd": "^5.25.1",
|
||||
"antd": "^5.27.3",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^4.3.0",
|
||||
"apollo-link-sentry": "^4.4.0",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.8.4",
|
||||
"axios": "^1.11.0",
|
||||
"classnames": "^2.5.1",
|
||||
"css-box-model": "^1.2.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"dayjs": "^1.11.18",
|
||||
"dayjs-business-days2": "^1.3.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"dotenv": "^17.2.2",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"graphql": "^16.11.0",
|
||||
"i18next": "^24.2.3",
|
||||
"i18next-browser-languagedetector": "^8.1.0",
|
||||
"i18next": "^25.5.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"libphonenumber-js": "^1.12.8",
|
||||
"libphonenumber-js": "^1.12.15",
|
||||
"logrocket": "^9.0.2",
|
||||
"markerjs2": "^2.32.4",
|
||||
"markerjs2": "^2.32.6",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-url": "^8.0.1",
|
||||
"normalize-url": "^8.0.2",
|
||||
"object-hash": "^3.0.0",
|
||||
"phone": "^3.1.67",
|
||||
"posthog-js": "^1.261.7",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^9.1.2",
|
||||
"query-string": "^9.2.2",
|
||||
"raf-schd": "^4.0.3",
|
||||
"react": "^18.3.1",
|
||||
"react-big-calendar": "^1.18.0",
|
||||
"react-big-calendar": "^1.19.4",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^8.0.1",
|
||||
"lightningcss": "^1.30.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-grid-gallery": "^1.0.1",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-i18next": "^15.7.3",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -69,7 +73,7 @@
|
||||
"react-resizable": "^3.0.5",
|
||||
"react-router-dom": "^6.30.0",
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-virtuoso": "^4.12.7",
|
||||
"react-virtuoso": "^4.14.0",
|
||||
"recharts": "^2.15.2",
|
||||
"redux": "^5.0.1",
|
||||
"redux-actions": "^3.0.3",
|
||||
@@ -77,9 +81,9 @@
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.1",
|
||||
"sass": "^1.88.0",
|
||||
"sass": "^1.92.0",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"styled-components": "^6.1.18",
|
||||
"styled-components": "^6.1.19",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"use-memo-one": "^1.1.3",
|
||||
"vite-plugin-ejs": "^1.7.0",
|
||||
@@ -106,7 +110,9 @@
|
||||
"test:e2e:rome": "playwright test --config playwright.rome.config.js",
|
||||
"test:e2e:imex:headed": "playwright test --config playwright.config.js --headed",
|
||||
"test:e2e:rome:headed": "playwright test --config playwright.rome.config.js --headed",
|
||||
"test:e2e:report": "playwright show-report"
|
||||
"test:e2e:report": "playwright show-report",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -130,37 +136,36 @@
|
||||
"@ant-design/icons": "^6.0.0",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@dotenvx/dotenvx": "^1.44.0",
|
||||
"@dotenvx/dotenvx": "^1.49.0",
|
||||
"@emotion/babel-plugin": "^11.13.5",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@eslint/js": "^9.26.0",
|
||||
"@playwright/test": "^1.51.1",
|
||||
"@sentry/webpack-plugin": "^3.4.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@eslint/js": "^9.33.0",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@sentry/webpack-plugin": "^4.1.1",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"browserslist": "^4.24.5",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"browserslist": "^4.25.3",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"chalk": "^5.4.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"chalk": "^5.6.0",
|
||||
"eslint": "^9.33.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"globals": "^15.15.0",
|
||||
"jsdom": "^26.0.0",
|
||||
"memfs": "^4.17.1",
|
||||
"memfs": "^4.36.3",
|
||||
"os-browserify": "^0.3.0",
|
||||
"playwright": "^1.51.1",
|
||||
"playwright": "^1.55.0",
|
||||
"react-error-overlay": "^6.1.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-babel": "^1.3.1",
|
||||
"vite": "^7.1.3",
|
||||
"vite-plugin-babel": "^1.3.2",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-node-polyfills": "^0.23.0",
|
||||
"vite-plugin-pwa": "^1.0.0",
|
||||
"vite-plugin-node-polyfills": "^0.24.0",
|
||||
"vite-plugin-pwa": "^1.0.3",
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"vitest": "^3.1.3",
|
||||
"vitest": "^3.2.4",
|
||||
"workbox-window": "^7.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ export default defineConfig({
|
||||
command: "npm run start:imex",
|
||||
ignoreHTTPSErrors: true,
|
||||
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
||||
// eslint-disable-next-line no-undef
|
||||
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
||||
}
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ export default defineConfig({
|
||||
command: "npm run start:rome",
|
||||
ignoreHTTPSErrors: true,
|
||||
url: "https://localhost:3000/health", // Health check endpoint will tell us when the server is ready
|
||||
// eslint-disable-next-line no-undef
|
||||
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Scripts for firebase and firebase messaging
|
||||
// eslint-disable-next-line no-undef
|
||||
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-app-compat.js");
|
||||
// eslint-disable-next-line no-undef
|
||||
importScripts("https://www.gstatic.com/firebasejs/10.14.1/firebase-messaging-compat.js");
|
||||
|
||||
// Initialize the Firebase app in the service worker by passing the generated config
|
||||
@@ -42,13 +44,16 @@ switch (this.location.hostname) {
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
firebase.initializeApp(firebaseConfig);
|
||||
|
||||
// Retrieve firebase messaging
|
||||
// eslint-disable-next-line no-undef
|
||||
const messaging = firebase.messaging();
|
||||
|
||||
messaging.onBackgroundMessage(function (payload) {
|
||||
// Customize notification here
|
||||
console.log("[firebase-messaging-sw.js] Received background message ", payload);
|
||||
// eslint-disable-next-line no-undef
|
||||
self.registration.showNotification(notificationTitle, notificationOptions);
|
||||
});
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { connect, useSelector } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
import { setDarkMode } from "../redux/application/application.actions";
|
||||
import { selectDarkMode } from "../redux/application/application.selectors";
|
||||
import { selectCurrentUser } from "../redux/user/user.selectors.js";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import themeProvider from "./themeProvider";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import getTheme from "./themeProvider";
|
||||
|
||||
// Base Split configuration
|
||||
const config = {
|
||||
@@ -24,19 +28,53 @@ const config = {
|
||||
function SplitClientProvider({ children }) {
|
||||
const imexshopid = useSelector((state) => state.user.imexshopid); // Access imexshopid from Redux store
|
||||
const splitClient = useSplitClient({ key: imexshopid || "anon" }); // Use imexshopid or fallback to "anon"
|
||||
|
||||
useEffect(() => {
|
||||
if (splitClient && imexshopid) {
|
||||
// Log readiness for debugging; no need for ready() since isReady is available
|
||||
console.log(`Split client initialized with key: ${imexshopid}, isReady: ${splitClient.isReady}`);
|
||||
}
|
||||
}, [splitClient, imexshopid]);
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
function AppContainer() {
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setDarkMode: (isDarkMode) => dispatch(setDarkMode(isDarkMode))
|
||||
});
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
function AppContainer({ currentUser, setDarkMode }) {
|
||||
const { t } = useTranslation();
|
||||
const isDarkMode = useSelector(selectDarkMode);
|
||||
const theme = useMemo(() => getTheme(isDarkMode), [isDarkMode]);
|
||||
|
||||
// Update data-theme attribute when dark mode changes
|
||||
useEffect(() => {
|
||||
document.documentElement.setAttribute("data-theme", isDarkMode ? "dark" : "light");
|
||||
return () => document.documentElement.removeAttribute("data-theme");
|
||||
}, [isDarkMode]);
|
||||
|
||||
// Sync Redux darkMode with localStorage on user change
|
||||
useEffect(() => {
|
||||
if (currentUser?.uid) {
|
||||
const savedMode = localStorage.getItem(`dark-mode-${currentUser.uid}`);
|
||||
if (savedMode !== null) {
|
||||
setDarkMode(JSON.parse(savedMode));
|
||||
} else {
|
||||
setDarkMode(false); // default to light mode
|
||||
}
|
||||
} else {
|
||||
setDarkMode(false);
|
||||
}
|
||||
}, [currentUser?.uid]);
|
||||
|
||||
// Persist darkMode to localStorage when it or user changes
|
||||
useEffect(() => {
|
||||
if (currentUser?.uid) {
|
||||
localStorage.setItem(`dark-mode-${currentUser.uid}`, JSON.stringify(isDarkMode));
|
||||
}
|
||||
}, [isDarkMode, currentUser?.uid]);
|
||||
|
||||
return (
|
||||
<CookiesProvider>
|
||||
@@ -44,10 +82,9 @@ function AppContainer() {
|
||||
<ConfigProvider
|
||||
input={{ autoComplete: "new-password" }}
|
||||
locale={enLocale}
|
||||
theme={themeProvider}
|
||||
theme={theme}
|
||||
form={{
|
||||
validateMessages: {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
required: t("general.validation.required", { label: "${label}" })
|
||||
}
|
||||
}}
|
||||
@@ -64,4 +101,4 @@ function AppContainer() {
|
||||
);
|
||||
}
|
||||
|
||||
export default Sentry.withProfiler(AppContainer);
|
||||
export default Sentry.withProfiler(connect(mapStateToProps, mapDispatchToProps)(AppContainer));
|
||||
|
||||
@@ -7,13 +7,14 @@ import { connect } from "react-redux";
|
||||
import { Route, Routes, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component"; // Component Imports
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
||||
import LandingPage from "../pages/landing/landing.page";
|
||||
import TechPageContainer from "../pages/tech/tech.page.container";
|
||||
import { setOnline } from "../redux/application/application.actions";
|
||||
import { selectOnline } from "../redux/application/application.selectors";
|
||||
import SimplifiedPartsPageContainer from "../pages/simplified-parts/simplified-parts.page.container.jsx";
|
||||
import { setIsPartsEntry, setOnline } from "../redux/application/application.actions";
|
||||
import { selectIsPartsEntry, selectOnline } from "../redux/application/application.selectors";
|
||||
import { checkUserSession } from "../redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
||||
import PrivateRoute from "../components/PrivateRoute";
|
||||
@@ -28,21 +29,31 @@ const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.
|
||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
||||
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
||||
const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container"));
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
online: selectOnline,
|
||||
bodyshop: selectBodyshop,
|
||||
currentEula: selectCurrentEula
|
||||
currentEula: selectCurrentEula,
|
||||
isPartsEntry: selectIsPartsEntry
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
checkUserSession: () => dispatch(checkUserSession()),
|
||||
setOnline: (isOnline) => dispatch(setOnline(isOnline))
|
||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||
setIsPartsEntry: (isParts) => dispatch(setIsPartsEntry(isParts))
|
||||
});
|
||||
|
||||
export function App({ bodyshop, checkUserSession, currentUser, online, setOnline, currentEula }) {
|
||||
export function App({
|
||||
bodyshop,
|
||||
checkUserSession,
|
||||
currentUser,
|
||||
online,
|
||||
setOnline,
|
||||
setIsPartsEntry,
|
||||
currentEula,
|
||||
isPartsEntry
|
||||
}) {
|
||||
const client = useSplitClient().client;
|
||||
const [listenersAdded, setListenersAdded] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
@@ -52,10 +63,15 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
if (!navigator.onLine) {
|
||||
setOnline(false);
|
||||
}
|
||||
|
||||
checkUserSession();
|
||||
}, [checkUserSession, setOnline]);
|
||||
|
||||
useEffect(() => {
|
||||
const pathname = window.location.pathname;
|
||||
const isParts = pathname === "/parts" || pathname.startsWith("/parts/");
|
||||
setIsPartsEntry(isParts);
|
||||
}, [setIsPartsEntry]);
|
||||
|
||||
//const b = Grid.useBreakpoint();
|
||||
// console.log("Breakpoints:", b);
|
||||
|
||||
@@ -144,6 +160,7 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
currentUser={currentUser}
|
||||
bodyshop={bodyshop}
|
||||
workspaceCode={bodyshop?.tours_enabled ? "9BkbEseqNqxw8jUH" : ""}
|
||||
isPartsEntry={isPartsEntry}
|
||||
/>
|
||||
|
||||
<NotificationProvider>
|
||||
@@ -188,14 +205,6 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
</ErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/mp/:paymentIs"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<MobilePaymentContainer />
|
||||
</ErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/manage/*"
|
||||
element={
|
||||
@@ -220,6 +229,16 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
>
|
||||
<Route path="*" element={<TechPageContainer />} />
|
||||
</Route>
|
||||
<Route
|
||||
path="/parts/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
</ErrorBoundary>
|
||||
}
|
||||
>
|
||||
<Route path="*" element={<SimplifiedPartsPageContainer />} />
|
||||
</Route>
|
||||
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized} />}>
|
||||
<Route path="*" element={<DocumentEditorContainer />} />
|
||||
</Route>
|
||||
|
||||
@@ -1,15 +1,226 @@
|
||||
//Global Styles.
|
||||
:root {
|
||||
--table-stripe-bg: #f4f4f4; /* Light mode table stripe */
|
||||
--menu-divider-color: #74695c; /* Light mode menu divider */
|
||||
--menu-submenu-text: rgba(255, 255, 255, 0.65); /* Light mode submenu text */
|
||||
--kanban-column-bg: #ddd; /* Light mode kanban column */
|
||||
--alert-color: blue; /* Light mode alert */
|
||||
--completion-soon-color: rgba(255, 140, 0, 0.8); /* Light mode completion soon */
|
||||
--completion-past-color: rgba(255, 0, 0, 0.8); /* Light mode completion past */
|
||||
--job-line-manual-color: tomato; /* Light mode job line manual */
|
||||
--muted-button-color: lightgray; /* Light mode muted button */
|
||||
--muted-button-hover-color: darkgrey; /* Light mode muted button hover */
|
||||
--table-border-color: #ddd; /* Light mode table border */
|
||||
--table-hover-bg: #f5f5f5; /* Light mode table hover */
|
||||
--popover-bg: #fff; /* Light mode popover background */
|
||||
--error-text: red; /* Light mode error message */
|
||||
--no-jobs-text: #888; /* Light mode no jobs message */
|
||||
--message-yours-bg: #eee; /* Light mode yours message background */
|
||||
--message-mine-bg-start: #00d0ea; /* Light mode mine message gradient start */
|
||||
--message-mine-bg-end: #0085d1; /* Light mode mine message gradient end */
|
||||
--message-mine-text: white; /* Light mode mine message text */
|
||||
--message-mine-tail-bg: white; /* Light mode mine/yours message tail */
|
||||
--system-message-bg: #f5f5f5; /* Light mode system message background */
|
||||
--system-message-text: #555; /* Light mode system message text */
|
||||
--system-label-text: #888; /* Light mode system label/date text */
|
||||
--message-icon-color: whitesmoke; /* Light mode message icon */
|
||||
--eula-card-bg: lightgray; /* Light mode eula card background */
|
||||
--notification-bg: #fff; /* Light mode notification background */
|
||||
--notification-text: rgba(0, 0, 0, 0.85); /* Light mode notification text */
|
||||
--notification-border: #d9d9d9; /* Light mode notification border */
|
||||
--notification-header-bg: #fafafa; /* Light mode notification header background */
|
||||
--notification-header-border: #f0f0f0; /* Light mode notification header border */
|
||||
--notification-header-text: rgba(0, 0, 0, 0.85); /* Light mode notification header text */
|
||||
--notification-toggle-icon: #1677ff; /* Light mode notification toggle icon */
|
||||
--notification-switch-bg: #1677ff; /* Light mode notification switch background */
|
||||
--notification-btn-link: #1677ff; /* Light mode notification link button */
|
||||
--notification-btn-link-hover: #69b1ff; /* Light mode notification link button hover */
|
||||
--notification-btn-link-disabled: rgba(0, 0, 0, 0.25); /* Light mode notification link button disabled */
|
||||
--notification-btn-link-active: #0958d9; /* Light mode notification link button active */
|
||||
--notification-read-bg: #fff; /* Light mode notification read background */
|
||||
--notification-read-text: rgba(0, 0, 0, 0.65); /* Light mode notification read text */
|
||||
--notification-unread-bg: #f5f5f5; /* Light mode notification unread background */
|
||||
--notification-unread-text: rgba(0, 0, 0, 0.85); /* Light mode notification unread text */
|
||||
--notification-item-hover-bg: #fafafa; /* Light mode notification item hover background */
|
||||
--notification-ro-number: #1677ff; /* Light mode notification RO number */
|
||||
--notification-relative-time: rgba(0, 0, 0, 0.45); /* Light mode notification relative time */
|
||||
--alert-bg: #fff1f0; /* Light mode alert background */
|
||||
--alert-text: rgba(0, 0, 0, 0.85); /* Light mode alert text */
|
||||
--alert-border: #ffa39e; /* Light mode alert border */
|
||||
--alert-message: #ff4d4f; /* Light mode alert message */
|
||||
--share-badge-bg: #cccccc; /* Light mode share badge background */
|
||||
--column-header-bg: #d0d0d0; /* Light mode column header background */
|
||||
--footer-bg: #d0d0d0; /* Light mode footer background */
|
||||
--tech-icon-color: orangered; /* Light mode tech icon color */
|
||||
--clone-border-color: #1890ff; /* Light mode clone border color */
|
||||
--event-arrived-bg: rgba(4, 141, 4, 0.4); /* Light mode arrived event background */
|
||||
--event-block-bg: tomato; /* Light mode block event background */
|
||||
--event-selected-bg: slategrey; /* Light mode selected event background */
|
||||
--task-bg: #fff; /* Light mode task center background */
|
||||
--task-text: rgba(0, 0, 0, 0.85); /* Light mode task text */
|
||||
--task-border: #d9d9d9; /* Light mode task border */
|
||||
--task-header-bg: #fafafa; /* Light mode task header background */
|
||||
--task-header-border: #f0f0f0; /* Light mode task header border */
|
||||
--task-section-bg: #f5f5f5; /* Light mode task section background */
|
||||
--task-section-border: #e8e8e8; /* Light mode task section border */
|
||||
--task-row-hover-bg: #f5f5f5; /* Light mode task row hover background */
|
||||
--task-row-border: #f0f0f0; /* Light mode task row border */
|
||||
--task-ro-number: #1677ff; /* Light mode task RO number */
|
||||
--task-due-text: rgba(0, 0, 0, 0.45); /* Light mode task due text */
|
||||
--task-button-bg: #1677ff; /* Light mode task button background */
|
||||
--task-button-hover-bg: #4096ff; /* Light mode task button hover background */
|
||||
--task-button-disabled-bg: #d9d9d9; /* Light mode task button disabled background */
|
||||
--task-button-text: white; /* Light mode task button text */
|
||||
--task-message-text: rgba(0, 0, 0, 0.45); /* Light mode task message text */
|
||||
--mask-bg: rgba(0, 0, 0, 0.05); /* Light mode mask background */
|
||||
--board-text-color: #393939; /* Light mode board text color */
|
||||
--section-bg: #e3e3e3; /* Light mode section background */
|
||||
--detail-text-color: #4d4d4d; /* Light mode detail text color */
|
||||
--card-selected-bg: rgba(128, 128, 128, 0.2); /* Light mode selected card background */
|
||||
--card-stripe-even-bg: #f0f2f5; /* Light mode even card background */
|
||||
--card-stripe-odd-bg: #ffffff; /* Light mode odd card background */
|
||||
--bar-border-color: #f0f2f5; /* Light mode bar border and background */
|
||||
--tag-wrapper-bg: #f0f2f5; /* Light mode tag wrapper background */
|
||||
--tag-wrapper-text: #000; /* Light mode tag wrapper text */
|
||||
--preview-bg: lightgray; /* Light mode preview background */
|
||||
--preview-border-color: #2196F3; /* Light mode preview border color */
|
||||
--event-bg-fallback: #c4c4c4; /* Light mode event background fallback */
|
||||
--card-bg-fallback: #ffffff; /* Light mode card background fallback */
|
||||
--card-text-fallback: black; /* Light mode card text fallback */
|
||||
--table-row-even-bg: rgb(236, 236, 236); /* Light mode table row even background */
|
||||
--status-row-bg-fallback: #ffffff; /* Light mode status row fallback background */
|
||||
--reset-link-color: #0000ff; /* Light mode reset link color */
|
||||
--error-header-text: tomato; /* Light mode error header text */
|
||||
--tooltip-bg: white; /* Light mode tooltip background */
|
||||
--tooltip-border: gray; /* Light mode tooltip border */
|
||||
--tooltip-text-fallback: black; /* Light mode tooltip text fallback */
|
||||
--teams-button-bg: #6264A7; /* Light mode Teams button background */
|
||||
--teams-button-border: #6264A7; /* Light mode Teams button border */
|
||||
--teams-button-text: #FFFFFF; /* Light mode Teams button text and icon */
|
||||
--content-bg: #fff; /* Light mode content background */
|
||||
--legend-bg-fallback: #ffffff; /* Light mode legend background fallback */
|
||||
--tech-content-bg: #fff; /* Light mode tech content background */
|
||||
--today-bg: #ffffff; /* Light mode today background */
|
||||
--today-text: #000000; /* Light mode today text */
|
||||
--off-range-bg: #f8f8f8; /* Light mode off-range background */
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--table-stripe-bg: #2a2a2a; /* Dark mode table stripe */
|
||||
--menu-divider-color: #5c5c5c; /* Dark mode menu divider */
|
||||
--menu-submenu-text: rgba(255, 255, 255, 0.85); /* Dark mode submenu text */
|
||||
--kanban-column-bg: #333333; /* Dark mode kanban column */
|
||||
--alert-color: #4da8ff; /* Dark mode alert */
|
||||
--completion-soon-color: #ff8c1a; /* Dark mode completion soon */
|
||||
--completion-past-color: #ff4d4f; /* Dark mode completion past */
|
||||
--job-line-manual-color: #ff6347; /* Dark mode job line manual */
|
||||
--muted-button-color: #666666; /* Dark mode muted button */
|
||||
--muted-button-hover-color: #999999; /* Dark mode muted button hover */
|
||||
--table-border-color: #5c5c5c; /* Dark mode table border */
|
||||
--table-hover-bg: #2a2a2a; /* Dark mode table hover */
|
||||
--popover-bg: #2a2a2a; /* Dark mode popover background */
|
||||
--error-text: #ff4d4f; /* Dark mode error message */
|
||||
--no-jobs-text: #999999; /* Dark mode no jobs message */
|
||||
--message-yours-bg: #2a2a2a; /* Dark mode yours message background */
|
||||
--message-mine-bg-start: #4da8ff; /* Dark mode mine message gradient start */
|
||||
--message-mine-bg-end: #326ade; /* Dark mode mine message gradient end */
|
||||
--message-mine-text: #ffffff; /* Dark mode mine message text */
|
||||
--message-mine-tail-bg: #1f1f1f; /* Dark mode mine/yours message tail */
|
||||
--system-message-bg: #333333; /* Dark mode system message background */
|
||||
--system-message-text: #cccccc; /* Dark mode system message text */
|
||||
--system-label-text: #999999; /* Dark mode system label/date text */
|
||||
--message-icon-color: #cccccc; /* Dark mode message icon */
|
||||
--eula-card-bg: #2a2a2a; /* Dark mode eula card background */
|
||||
--notification-bg: #2a2a2a; /* Dark mode notification background */
|
||||
--notification-text: rgba(255, 255, 255, 0.85); /* Dark mode notification text */
|
||||
--notification-border: #5c5c5c; /* Dark mode notification border */
|
||||
--notification-header-bg: #333333; /* Dark mode notification header background */
|
||||
--notification-header-border: #444444; /* Dark mode notification header border */
|
||||
--notification-header-text: rgba(255, 255, 255, 0.85); /* Dark mode notification header text */
|
||||
--notification-toggle-icon: #4da8ff; /* Dark mode notification toggle icon */
|
||||
--notification-switch-bg: #4da8ff; /* Dark mode notification switch background */
|
||||
--notification-btn-link: #4da8ff; /* Dark mode notification link button */
|
||||
--notification-btn-link-hover: #80c1ff; /* Dark mode notification link button hover */
|
||||
--notification-btn-link-disabled: rgba(255, 255, 255, 0.25); /* Dark mode notification link button disabled */
|
||||
--notification-btn-link-active: #2681ff; /* Dark mode notification link button active */
|
||||
--notification-read-bg: #2a2a2a; /* Dark mode notification read background */
|
||||
--notification-read-text: rgba(255, 255, 255, 0.65); /* Dark mode notification read text */
|
||||
--notification-unread-bg: #333333; /* Dark mode notification unread background */
|
||||
--notification-unread-text: rgba(255, 255, 255, 0.85); /* Dark mode notification unread text */
|
||||
--notification-item-hover-bg: #3a3a3a; /* Dark mode notification item hover background */
|
||||
--notification-ro-number: #4da8ff; /* Dark mode notification RO number */
|
||||
--notification-relative-time: rgba(255, 255, 255, 0.45); /* Dark mode notification relative time */
|
||||
--alert-bg: #3a1a1a; /* Dark mode alert background */
|
||||
--alert-text: rgba(255, 255, 255, 0.85); /* Dark mode alert text */
|
||||
--alert-border: #ff6666; /* Dark mode alert border */
|
||||
--alert-message: #ff6666; /* Dark mode alert message */
|
||||
--share-badge-bg: #666666; /* Dark mode share badge background */
|
||||
--column-header-bg: #333333; /* Dark mode column header background */
|
||||
--footer-bg: #333333; /* Dark mode footer background */
|
||||
--tech-icon-color: #ff4500; /* Dark mode tech icon color */
|
||||
--clone-border-color: #4da8ff; /* Dark mode clone border color */
|
||||
--event-arrived-bg: rgba(4, 141, 4, 0.6); /* Dark mode arrived event background */
|
||||
--event-block-bg: tomato; /* Dark mode block event background */
|
||||
--event-selected-bg: #4a5e6e; /* Dark mode selected event background */
|
||||
--task-bg: #2a2a2a; /* Dark mode task center background */
|
||||
--task-text: rgba(255, 255, 255, 0.85); /* Dark mode task text */
|
||||
--task-border: #5c5c5c; /* Dark mode task border */
|
||||
--task-header-bg: #333333; /* Dark mode task header background */
|
||||
--task-header-border: #444444; /* Dark mode task header border */
|
||||
--task-section-bg: #333333; /* Dark mode task section background */
|
||||
--task-section-border: #444444; /* Dark mode task section border */
|
||||
--task-row-hover-bg: #3a3a3a; /* Dark mode task row hover background */
|
||||
--task-row-border: #444444; /* Dark mode task row border */
|
||||
--task-ro-number: #4da8ff; /* Dark mode task RO number */
|
||||
--task-due-text: rgba(255, 255, 255, 0.45); /* Dark mode task due text */
|
||||
--task-button-bg: #4da8ff; /* Dark mode task button background */
|
||||
--task-button-hover-bg: #80c1ff; /* Dark mode task button hover background */
|
||||
--task-button-disabled-bg: #666666; /* Dark mode task button disabled background */
|
||||
--task-button-text: #ffffff; /* Dark mode task button text */
|
||||
--task-message-text: rgba(255, 255, 255, 0.45); /* Dark mode task message text */
|
||||
--mask-bg: rgba(255, 255, 255, 0.05); /* Dark mode mask background */
|
||||
--board-text-color: #cccccc; /* Dark mode board text color */
|
||||
--section-bg: #333333; /* Dark mode section background */
|
||||
--detail-text-color: #bbbbbb; /* Dark mode detail text color */
|
||||
--card-selected-bg: rgba(255, 255, 255, 0.1); /* Dark mode selected card background */
|
||||
--card-stripe-even-bg: #2a2a2a; /* Dark mode even card background */
|
||||
--card-stripe-odd-bg: #1f1f1f; /* Dark mode odd card background */
|
||||
--bar-border-color: #2a2a2a; /* Dark mode bar border and background */
|
||||
--tag-wrapper-bg: #2a2a2a; /* Dark mode tag wrapper background */
|
||||
--tag-wrapper-text: #cccccc; /* Dark mode tag wrapper text */
|
||||
--preview-bg: #2a2a2a; /* Dark mode preview background */
|
||||
--preview-border-color: #4da8ff; /* Dark mode preview border color */
|
||||
--event-bg-fallback: #262626; /* Dark mode event background fallback */
|
||||
--card-bg-fallback: #2a2a2a; /* Dark mode card background fallback */
|
||||
--card-text-fallback: #cccccc; /* Dark mode card text fallback */
|
||||
--table-row-even-bg: #2a2a2a; /* Dark mode table row even background */
|
||||
--status-row-bg-fallback: #1f1f1f; /* Dark mode status row fallback background */
|
||||
--reset-link-color: #4da8ff; /* Dark mode reset link color */
|
||||
--error-header-text: #ff6347; /* Dark mode error header text */
|
||||
--tooltip-bg: #2a2a2a; /* Dark mode tooltip background */
|
||||
--tooltip-border: #5c5c5c; /* Dark mode tooltip border */
|
||||
--tooltip-text-fallback: #cccccc; /* Dark mode tooltip text fallback */
|
||||
--teams-button-bg: #7b7dc4; /* Dark mode Teams button background */
|
||||
--teams-button-border: #7b7dc4; /* Dark mode Teams button border */
|
||||
--teams-button-text: #ffffff; /* Dark mode Teams button text and icon */
|
||||
--content-bg: #2a2a2a; /* Dark mode content background */
|
||||
--legend-bg-fallback: #2a2a2a; /* Dark mode legend background fallback */
|
||||
--tech-content-bg: #2a2a2a; /* Dark mode tech content background */
|
||||
--today-bg: #4a5e6e; /* Dark mode today background */
|
||||
--today-text: #ffffff; /* Dark mode today text */
|
||||
--off-range-bg: #333333; /* Dark mode off-range background */
|
||||
--svg-background: #FFF; /* Dark mode SVG background */
|
||||
}
|
||||
|
||||
// Global Styles
|
||||
@import "react-big-calendar/lib/sass/styles";
|
||||
|
||||
.ant-menu-item-divider {
|
||||
border-bottom: 1px solid #74695c !important;
|
||||
border-bottom: 1px solid var(--menu-divider-color) !important;
|
||||
}
|
||||
|
||||
// TODO: This was added because the newest release of ant was making the text color and the background color the same on a selected header
|
||||
// Tried all available tokens (https://ant.design/components/menu?locale=en-US) and even reverted all our custom styles, to no avail
|
||||
// This should be kept an eye on, especially if implementing DARK MODE
|
||||
// Note: Monitor this in dark mode to ensure text visibility
|
||||
.ant-menu-submenu-title {
|
||||
color: rgba(255, 255, 255, 0.65) !important;
|
||||
color: var(--menu-submenu-text) !important;
|
||||
}
|
||||
|
||||
.imex-table-header {
|
||||
@@ -46,7 +257,7 @@
|
||||
}
|
||||
|
||||
.ellipses {
|
||||
display: inline-block; /* for em, a, span, etc (inline by default) */
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
width: calc(95%);
|
||||
overflow: hidden;
|
||||
@@ -60,23 +271,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ::-webkit-scrollbar-track {
|
||||
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
// border-radius: 0.2rem;
|
||||
// background-color: #f5f5f5;
|
||||
// }
|
||||
// Scrollbar styles (uncomment if needed, updated for dark mode)
|
||||
::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 0.2rem;
|
||||
background-color: var(--table-stripe-bg);
|
||||
}
|
||||
|
||||
// ::-webkit-scrollbar {
|
||||
// width: 0.25rem;
|
||||
// max-height: 0.25rem;
|
||||
// background-color: #f5f5f5;
|
||||
// }
|
||||
::-webkit-scrollbar {
|
||||
width: 0.25rem;
|
||||
max-height: 0.25rem;
|
||||
background-color: var(--table-stripe-bg);
|
||||
}
|
||||
|
||||
// ::-webkit-scrollbar-thumb {
|
||||
// border-radius: 0.2rem;
|
||||
// -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
// background-color: #188fff;
|
||||
// }
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 0.2rem;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
background-color: var(--alert-color);
|
||||
}
|
||||
|
||||
.ant-input-number-input,
|
||||
.ant-input-number,
|
||||
@@ -88,28 +300,27 @@
|
||||
|
||||
.production-alert {
|
||||
animation: alertBlinker 1s linear infinite;
|
||||
color: blue;
|
||||
color: var(--alert-color);
|
||||
}
|
||||
|
||||
@keyframes alertBlinker {
|
||||
50% {
|
||||
color: red;
|
||||
color: var(--completion-past-color);
|
||||
opacity: 100;
|
||||
//opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: blue;
|
||||
color: var(--alert-color);
|
||||
}
|
||||
|
||||
.production-completion-soon {
|
||||
color: rgba(255, 140, 0, 0.8);
|
||||
color: var(--completion-soon-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.production-completion-past {
|
||||
color: rgba(255, 0, 0, 0.8);
|
||||
color: var(--completion-past-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -139,7 +350,7 @@
|
||||
}
|
||||
|
||||
.react-kanban-column {
|
||||
background-color: #ddd !important;
|
||||
background-color: var(--kanban-column-bg) !important;
|
||||
}
|
||||
|
||||
.production-list-table {
|
||||
@@ -151,18 +362,18 @@
|
||||
.ReactGridGallery_tile-icon-bar {
|
||||
div {
|
||||
svg {
|
||||
fill: #1890ff;
|
||||
fill: var(--alert-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-line-manual {
|
||||
color: tomato;
|
||||
color: var(--job-line-manual-color);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
||||
background-color: #f4f4f4;
|
||||
background-color: var(--table-stripe-bg);
|
||||
}
|
||||
|
||||
.rowWithColor > td {
|
||||
@@ -170,15 +381,15 @@
|
||||
}
|
||||
|
||||
.muted-button {
|
||||
color: lightgray;
|
||||
color: var(--muted-button-color);
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px; /* Adjust as needed */
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.muted-button:hover {
|
||||
color: darkgrey;
|
||||
color: var(--muted-button-hover-color);
|
||||
}
|
||||
|
||||
.notification-alert-unordered-list {
|
||||
@@ -190,3 +401,31 @@
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
// Override react-big-calendar styles for dark mode only
|
||||
[data-theme="dark"] {
|
||||
.car-svg {
|
||||
background-color: var(--svg-background);
|
||||
}
|
||||
|
||||
.rbc-today {
|
||||
background-color: var(--today-bg);
|
||||
color: var(--today-text);
|
||||
}
|
||||
|
||||
.rbc-off-range {
|
||||
background-color: var(--off-range-bg);
|
||||
}
|
||||
|
||||
.rbc-day-bg.rbc-today {
|
||||
background-color: var(--today-bg);
|
||||
}
|
||||
}
|
||||
|
||||
//.rbc-time-header-gutter {
|
||||
// padding: 0;
|
||||
//}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import { memo } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { ProductFruits } from "react-product-fruits";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const ProductFruitsWrapper = React.memo(({ currentUser, bodyshop, workspaceCode }) => {
|
||||
const ProductFruitsWrapper = memo(({ currentUser, bodyshop, workspaceCode, isPartsEntry }) => {
|
||||
const featureProps = bodyshop?.features
|
||||
? Object.entries(bodyshop.features).reduce((acc, [key, value]) => {
|
||||
acc[key] = value === true || (typeof value === "string" && dayjs(value).isAfter(dayjs()));
|
||||
@@ -12,6 +12,7 @@ const ProductFruitsWrapper = React.memo(({ currentUser, bodyshop, workspaceCode
|
||||
: {};
|
||||
|
||||
return (
|
||||
!isPartsEntry &&
|
||||
workspaceCode &&
|
||||
currentUser?.authorized === true &&
|
||||
currentUser?.email && (
|
||||
@@ -30,6 +31,8 @@ const ProductFruitsWrapper = React.memo(({ currentUser, bodyshop, workspaceCode
|
||||
);
|
||||
});
|
||||
|
||||
ProductFruitsWrapper.displayName = "ProductFruitsWrapper";
|
||||
|
||||
export default ProductFruitsWrapper;
|
||||
|
||||
ProductFruitsWrapper.propTypes = {
|
||||
|
||||
@@ -4,36 +4,42 @@ import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
|
||||
const { defaultAlgorithm, darkAlgorithm } = theme;
|
||||
|
||||
let isDarkMode = false;
|
||||
|
||||
/**
|
||||
* Default theme
|
||||
* @type {{components: {Menu: {itemDividerBorderColor: string}}}}
|
||||
*/
|
||||
const defaultTheme = {
|
||||
const defaultTheme = (isDarkMode) => ({
|
||||
components: {
|
||||
Table: {
|
||||
rowHoverBg: "#e7f3ff",
|
||||
rowSelectedBg: "#e6f7ff",
|
||||
rowHoverBg: isDarkMode ? "#2a2a2a" : "#e7f3ff",
|
||||
rowSelectedBg: isDarkMode ? "#333333" : "#e6f7ff",
|
||||
headerSortHoverBg: "transparent"
|
||||
},
|
||||
Menu: {
|
||||
darkItemHoverBg: "#1890ff",
|
||||
itemHoverBg: "#1890ff",
|
||||
horizontalItemHoverBg: "#1890ff"
|
||||
darkItemHoverBg: isDarkMode ? "#004a77" : "#1890ff",
|
||||
itemHoverBg: isDarkMode ? "#004a77" : "#1890ff",
|
||||
horizontalItemHoverBg: isDarkMode ? "#004a77" : "#1890ff"
|
||||
}
|
||||
},
|
||||
token: {
|
||||
colorPrimary: InstanceRenderMgr({
|
||||
imex: "#1890ff",
|
||||
rome: "#326ade"
|
||||
}),
|
||||
colorInfo: InstanceRenderMgr({
|
||||
imex: "#1890ff",
|
||||
rome: "#326ade"
|
||||
})
|
||||
colorPrimary: InstanceRenderMgr(
|
||||
{
|
||||
imex: isDarkMode ? "#4da8ff" : "#1890ff",
|
||||
rome: isDarkMode ? "#5b8ce6" : "#326ade"
|
||||
},
|
||||
isDarkMode
|
||||
),
|
||||
colorInfo: InstanceRenderMgr(
|
||||
{
|
||||
imex: isDarkMode ? "#4da8ff" : "#1890ff",
|
||||
rome: isDarkMode ? "#5b8ce6" : "#326ade"
|
||||
},
|
||||
isDarkMode
|
||||
),
|
||||
colorError: isDarkMode ? "#ff4d4f" : "#f5222d",
|
||||
colorBgBase: isDarkMode ? "#1f1f1f" : "#ffffff" // Align with Ant Design dark mode
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Development theme
|
||||
@@ -60,8 +66,9 @@ const prodTheme = {};
|
||||
|
||||
const currentTheme = import.meta.env.DEV ? devTheme : prodTheme;
|
||||
|
||||
const finaltheme = {
|
||||
const getTheme = (isDarkMode) => ({
|
||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||
...defaultsDeep(currentTheme, defaultTheme)
|
||||
};
|
||||
export default finaltheme;
|
||||
});
|
||||
|
||||
export default getTheme;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
function PrivateRoute({ component: Component, isAuthorized, ...rest }) {
|
||||
function PrivateRoute({ isAuthorized }) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Button } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Card, Checkbox, Input, Space, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -16,12 +16,13 @@ import PayableExportAll from "../payable-export-all-button/payable-export-all-bu
|
||||
import PayableExportButton from "../payable-export-button/payable-export-button.component";
|
||||
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
import useLocalStorage from "./../../utils/useLocalStorage";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
@@ -31,7 +32,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
||||
const { t } = useTranslation();
|
||||
const [selectedBills, setSelectedBills] = useState([]);
|
||||
const [transInProgress, setTransInProgress] = useState(false);
|
||||
const [state, setState] = useState({
|
||||
const [state, setState] = useLocalStorage("accounting-payables-table-state", {
|
||||
sortedInfo: {},
|
||||
search: ""
|
||||
});
|
||||
@@ -181,7 +182,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
||||
onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelectAll: (selected, selectedRows) => setSelectedBills(selectedRows.map((i) => i.id)),
|
||||
onSelect: (record, selected, selectedRows, nativeEvent) => {
|
||||
onSelect: (record, selected, selectedRows) => {
|
||||
setSelectedBills(selectedRows.map((i) => i.id));
|
||||
},
|
||||
getCheckboxProps: (record) => ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Card, Input, Space, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -10,6 +10,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { exportPageLimit } from "../../utils/config";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
||||
@@ -21,7 +22,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
@@ -31,7 +32,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
||||
const { t } = useTranslation();
|
||||
const [selectedPayments, setSelectedPayments] = useState([]);
|
||||
const [transInProgress, setTransInProgress] = useState(false);
|
||||
const [state, setState] = useState({
|
||||
const [state, setState] = useLocalStorage("accounting-payments-table-state", {
|
||||
sortedInfo: {},
|
||||
search: ""
|
||||
});
|
||||
@@ -194,7 +195,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
||||
onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelectAll: (selected, selectedRows) => setSelectedPayments(selectedRows.map((i) => i.id)),
|
||||
onSelect: (record, selected, selectedRows, nativeEvent) => {
|
||||
onSelect: (record, selected, selectedRows) => {
|
||||
setSelectedPayments(selectedRows.map((i) => i.id));
|
||||
},
|
||||
getCheckboxProps: (record) => ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, Card, Input, Space, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -10,6 +10,7 @@ import { exportPageLimit } from "../../utils/config";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
||||
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
||||
@@ -20,7 +21,7 @@ import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AccountingReceivablesTableComponent);
|
||||
@@ -30,7 +31,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
||||
const [selectedJobs, setSelectedJobs] = useState([]);
|
||||
const [transInProgress, setTransInProgress] = useState(false);
|
||||
|
||||
const [state, setState] = useState({
|
||||
const [state, setState] = useLocalStorage("accounting-receivables-table-state", {
|
||||
sortedInfo: {},
|
||||
search: ""
|
||||
});
|
||||
@@ -207,7 +208,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
||||
onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelectAll: (selected, selectedRows) => setSelectedJobs(selectedRows.map((i) => i.id)),
|
||||
onSelect: (record, selected, selectedRows, nativeEvent) => {
|
||||
onSelect: (record, selected, selectedRows) => {
|
||||
setSelectedJobs(selectedRows.map((i) => i.id));
|
||||
},
|
||||
getCheckboxProps: (record) => ({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Alert } from "antd";
|
||||
import React from "react";
|
||||
|
||||
export default function AlertComponent(props) {
|
||||
return <Alert {...props} />;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Button, InputNumber, Popover, Select } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import AllocationsAssignmentComponent from "./allocations-assignment.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||
@@ -18,7 +18,7 @@ export default function AllocationsAssignmentContainer({ jobLineId, hours, refet
|
||||
|
||||
const handleAssignment = () => {
|
||||
insertAllocation({ variables: { alloc: { ...assignment } } })
|
||||
.then((r) => {
|
||||
.then(() => {
|
||||
notification["success"]({
|
||||
message: t("allocations.successes.save")
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Button, Popover, Select } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import AllocationsBulkAssignment from "./allocations-bulk-assignment.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { INSERT_ALLOCATION } from "../../graphql/allocations.queries";
|
||||
@@ -24,7 +24,7 @@ export default function AllocationsBulkAssignmentContainer({ jobLines, refetch }
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
insertAllocation({ variables: { alloc: allocs } }).then((r) => {
|
||||
insertAllocation({ variables: { alloc: allocs } }).then(() => {
|
||||
notification["success"]({
|
||||
message: t("employees.successes.save")
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Icon from "@ant-design/icons";
|
||||
import React from "react";
|
||||
import { MdRemoveCircleOutline } from "react-icons/md";
|
||||
|
||||
export default function AllocationsLabelComponent({ allocation, handleClick }) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { DELETE_ALLOCATION } from "../../graphql/allocations.queries";
|
||||
import AllocationsLabelComponent from "./allocations-employee-label.component";
|
||||
@@ -13,13 +12,13 @@ export default function AllocationsLabelContainer({ allocation, refetch }) {
|
||||
const handleClick = (e) => {
|
||||
e.preventDefault();
|
||||
deleteAllocation({ variables: { id: allocation.id } })
|
||||
.then((r) => {
|
||||
.then(() => {
|
||||
notification["success"]({
|
||||
message: t("allocations.successes.deleted")
|
||||
});
|
||||
if (refetch) refetch();
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch(() => {
|
||||
notification["error"]({ message: t("allocations.errors.deleting") });
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Table } from "antd";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import AuditTrailListComponent from "./audit-trail-list.component";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { List } from "antd";
|
||||
import Icon from "@ant-design/icons";
|
||||
import { FaArrowRight } from "react-icons/fa";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Popover, Tag } from "antd";
|
||||
import React from "react";
|
||||
import Barcode from "react-barcode";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||
import "./bill-cm-returns-table.styles.scss";
|
||||
@@ -33,7 +33,7 @@ export default function BillCmdReturnsTableComponent({ form, returnLoading, retu
|
||||
|
||||
return (
|
||||
<Form.List name="outstanding_returns">
|
||||
{(fields, { add, remove, move }) => {
|
||||
{(fields) => {
|
||||
return (
|
||||
<>
|
||||
<Typography.Title level={4}>{t("bills.labels.creditsnotreceived")}</Typography.Title>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0px !important;
|
||||
@@ -14,6 +14,6 @@
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
background-color: var(--table-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Popconfirm } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DELETE_BILL } from "../../graphql/bills.queries";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
@@ -43,7 +43,7 @@ export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) {
|
||||
}
|
||||
});
|
||||
|
||||
if (!!!result.errors) {
|
||||
if (!result.errors) {
|
||||
notification["success"]({ message: t("bills.successes.deleted") });
|
||||
insertAuditTrail({
|
||||
jobid: jobid,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router-dom";
|
||||
@@ -10,7 +10,6 @@ import { createStructuredSelector } from "reselect";
|
||||
import { DELETE_BILL_LINE, INSERT_NEW_BILL_LINES, UPDATE_BILL_LINE } from "../../graphql/bill-lines.queries";
|
||||
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import dayjs from "../../utils/day";
|
||||
@@ -28,13 +27,12 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditcontainer);
|
||||
|
||||
export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail, bodyshop }) {
|
||||
export function BillDetailEditcontainer({ insertAuditTrail, bodyshop }) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
|
||||
const { t } = useTranslation();
|
||||
@@ -48,7 +46,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
|
||||
variables: { billid: search.billid },
|
||||
skip: !!!search.billid,
|
||||
skip: !search.billid,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
@@ -71,7 +69,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
setUpdateLoading(true);
|
||||
//let adjustmentsToInsert = {};
|
||||
|
||||
const { billlines, upload, ...bill } = values;
|
||||
const { billlines, ...bill } = values;
|
||||
const updates = [];
|
||||
updates.push(
|
||||
update_bill({
|
||||
@@ -98,6 +96,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
});
|
||||
|
||||
billlines.forEach((billline) => {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline;
|
||||
delete il.__typename;
|
||||
|
||||
@@ -152,8 +151,8 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||
|
||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
||||
const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse;
|
||||
const exported = data?.bills_by_pk && data.bills_by_pk.exported;
|
||||
const isinhouse = data?.bills_by_pk && data.bills_by_pk.isinhouse;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -183,8 +182,8 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
|
||||
<BillMarkExportedButton bill={data && data.bills_by_pk} />
|
||||
<BillReeportButtonComponent bill={data?.bills_by_pk} />
|
||||
<BillMarkExportedButton bill={data?.bills_by_pk} />
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
@@ -220,11 +219,11 @@ const transformData = (data) => {
|
||||
billlines: data.bills_by_pk.billlines.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
joblineid: !!i.joblineid ? i.joblineid : "noline",
|
||||
joblineid: i.joblineid ? i.joblineid : "noline",
|
||||
applicable_taxes: {
|
||||
federal: (i.applicable_taxes && i.applicable_taxes.federal) || false,
|
||||
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
|
||||
local: (i.applicable_taxes && i.applicable_taxes.local) || false
|
||||
federal: i.applicable_taxes?.federal || false,
|
||||
state: i.applicable_taxes?.state || false,
|
||||
local: i.applicable_taxes?.local || false
|
||||
}
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
import { Button, Checkbox, Form, Modal } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPartsOrderContext: (context) =>
|
||||
dispatch(
|
||||
@@ -20,20 +17,12 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
context: context,
|
||||
modal: "partsOrder"
|
||||
})
|
||||
),
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditReturn);
|
||||
|
||||
export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, bodyshop, data, disabled }) {
|
||||
export function BillDetailEditReturn({ setPartsOrderContext, data, disabled }) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
@@ -86,9 +75,9 @@ export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, b
|
||||
title={t("bills.actions.return")}
|
||||
onOk={() => form.submit()}
|
||||
>
|
||||
<Form initialValues={data && data.bills_by_pk} onFinish={handleFinish} form={form}>
|
||||
<Form initialValues={data?.bills_by_pk} onFinish={handleFinish} form={form}>
|
||||
<Form.List name={["billlines"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
{(fields) => {
|
||||
return (
|
||||
<table style={{ tableLayout: "auto", width: "100%" }}>
|
||||
<thead>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Drawer, Grid } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import BillDetailEditComponent from "./bill-detail-edit-component";
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Checkbox, Form, Modal, Space } from "antd";
|
||||
import _ from "lodash";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
|
||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
||||
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
||||
@@ -24,7 +25,7 @@ import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { handleUpload as handleUploadToImageProxy } from "../documents-upload-imgproxy/documents-upload-imgproxy.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
billEnterModal: selectBillEnterModal,
|
||||
@@ -53,10 +54,10 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
const notification = useNotification();
|
||||
|
||||
const {
|
||||
treatments: { Enhanced_Payroll }
|
||||
treatments: { Enhanced_Payroll, Imgproxy }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
names: ["Enhanced_Payroll", "Imgproxy"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
@@ -84,6 +85,8 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { upload, location, outstanding_returns, inventory, federal_tax_exempt, ...remainingValues } = values;
|
||||
|
||||
let adjustmentsToInsert = {};
|
||||
@@ -101,9 +104,13 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
const {
|
||||
deductedfromlbr,
|
||||
lbr_adjustment,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
location: lineLocation,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
part_type,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
create_ppc,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
original_actual_price,
|
||||
...restI
|
||||
} = i;
|
||||
@@ -196,7 +203,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
job: { lbr_adjustments: newAdjustments }
|
||||
}
|
||||
});
|
||||
if (!!jobUpdate.errors) {
|
||||
if (jobUpdate.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
message: JSON.stringify(jobUpdate.errors)
|
||||
@@ -213,7 +220,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
variables: { partsLineIds: markPolReceived.map((p) => p.id) },
|
||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"]
|
||||
});
|
||||
if (!!r2.errors) {
|
||||
if (r2.errors) {
|
||||
setLoading(false);
|
||||
setEnterAgain(false);
|
||||
notification["error"]({
|
||||
@@ -224,7 +231,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
}
|
||||
}
|
||||
|
||||
if (!!r1.errors) {
|
||||
if (r1.errors) {
|
||||
setLoading(false);
|
||||
setEnterAgain(false);
|
||||
notification["error"]({
|
||||
@@ -244,7 +251,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
consumedbybillid: billId
|
||||
}
|
||||
});
|
||||
if (!!r2.errors) {
|
||||
if (r2.errors) {
|
||||
setLoading(false);
|
||||
setEnterAgain(false);
|
||||
notification["error"]({
|
||||
@@ -298,20 +305,39 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
upload.forEach((u) => {
|
||||
handleUpload(
|
||||
{ file: u.originFileObj },
|
||||
{
|
||||
bodyshop: bodyshop,
|
||||
uploaded_by: currentUser.email,
|
||||
jobId: values.jobid,
|
||||
billId: billId,
|
||||
tagsArray: null,
|
||||
callback: null
|
||||
},
|
||||
notification
|
||||
);
|
||||
});
|
||||
//Check if using Imgproxy or cloudinary
|
||||
|
||||
if (Imgproxy.treatment === "on") {
|
||||
upload.forEach((u) => {
|
||||
handleUploadToImageProxy(
|
||||
{ file: u.originFileObj },
|
||||
{
|
||||
bodyshop: bodyshop,
|
||||
uploaded_by: currentUser.email,
|
||||
jobId: values.jobid,
|
||||
billId: billId,
|
||||
tagsArray: null,
|
||||
callback: null
|
||||
},
|
||||
notification
|
||||
);
|
||||
});
|
||||
} else {
|
||||
upload.forEach((u) => {
|
||||
handleUpload(
|
||||
{ file: u.originFileObj },
|
||||
{
|
||||
bodyshop: bodyshop,
|
||||
uploaded_by: currentUser.email,
|
||||
jobId: values.jobid,
|
||||
billId: billId,
|
||||
tagsArray: null,
|
||||
callback: null
|
||||
},
|
||||
notification
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
///////////////////////////
|
||||
@@ -396,7 +422,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
{t("bills.labels.generatepartslabel")}
|
||||
</Checkbox>
|
||||
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
|
||||
<Button loading={loading} onClick={() => form.submit()}>
|
||||
<Button loading={loading} onClick={() => form.submit()} id="save-bill-enter-modal">
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
{billEnterModal.context && billEnterModal.context.id ? null : (
|
||||
@@ -406,6 +432,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
||||
onClick={() => {
|
||||
setEnterAgain(true);
|
||||
}}
|
||||
id="save-and-new-bill-enter-modal"
|
||||
>
|
||||
{t("general.actions.saveandnew")}
|
||||
</Button>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Form, Input, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import BillFormItemsExtendedFormItem from "./bill-form-lines.extended.formitem.component";
|
||||
|
||||
export default function BillFormLinesExtended({ lineData, discount, form, responsibilityCenters, disabled }) {
|
||||
export default function BillFormLinesExtended({ lineData, discount, form, responsibilityCenters }) {
|
||||
const [search, setSearch] = useState("");
|
||||
const { t } = useTranslation();
|
||||
const columns = [
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { MinusCircleFilled, PlusCircleFilled, WarningOutlined } from "@ant-design/icons";
|
||||
import { Button, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -12,7 +11,7 @@ import CiecaSelect from "../../utils/Ciecaselect";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BillFormItemsExtendedFormItem);
|
||||
@@ -22,7 +21,6 @@ export function BillFormItemsExtendedFormItem({
|
||||
bodyshop,
|
||||
form,
|
||||
record,
|
||||
index,
|
||||
disabled,
|
||||
responsibilityCenters,
|
||||
discount
|
||||
@@ -78,7 +76,7 @@ export function BillFormItemsExtendedFormItem({
|
||||
...billlineskeys,
|
||||
[record.id]: {
|
||||
...billlineskeys[billlineskeys],
|
||||
actual_cost: !!billlineskeys[billlineskeys].actual_cost
|
||||
actual_cost: billlineskeys[billlineskeys].actual_cost
|
||||
? billlineskeys[billlineskeys].actual_cost
|
||||
: Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100
|
||||
}
|
||||
@@ -93,7 +91,7 @@ export function BillFormItemsExtendedFormItem({
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
const line = value;
|
||||
if (!!!line) return null;
|
||||
if (!line) return null;
|
||||
const lineDiscount = (1 - Math.round((line.actual_cost / line.actual_price) * 100) / 100).toPrecision(2);
|
||||
|
||||
if (lineDiscount - discount === 0) return <div />;
|
||||
|
||||
@@ -2,7 +2,7 @@ import Icon, { UploadOutlined } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MdOpenInNew } from "react-icons/md";
|
||||
import { connect } from "react-redux";
|
||||
@@ -26,7 +26,7 @@ import DateTimePicker from "../form-date-time-picker/form-date-time-picker.compo
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export function BillFormComponent({
|
||||
bodyshop,
|
||||
@@ -254,7 +254,7 @@ export function BillFormComponent({
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
() => ({
|
||||
validator(rule, value) {
|
||||
if (ClosingPeriod.treatment === "on" && bodyshop.accountingconfig.ClosingPeriod) {
|
||||
if (
|
||||
@@ -374,8 +374,10 @@ export function BillFormComponent({
|
||||
let totals;
|
||||
if (!!values.total && !!values.billlines && values.billlines.length > 0)
|
||||
totals = CalculateBillTotal(values);
|
||||
if (!!totals)
|
||||
if (totals)
|
||||
return (
|
||||
// TODO: Align is not correct
|
||||
// eslint-disable-next-line react/no-unknown-property
|
||||
<div align="right">
|
||||
<Space size="large" wrap>
|
||||
<Statistic title={t("bills.labels.subtotal")} value={totals.subtotal.toFormat()} precision={2} />
|
||||
@@ -458,7 +460,7 @@ export function BillFormComponent({
|
||||
if (Array.isArray(e)) {
|
||||
return e;
|
||||
}
|
||||
return e && e.fileList;
|
||||
return e?.fileList;
|
||||
}}
|
||||
>
|
||||
<Upload.Dragger multiple={true} name="logo" beforeUpload={() => false} listType="picture">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -16,7 +15,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
@@ -27,8 +26,7 @@ export function BillEnterModalLinesComponent({
|
||||
discount,
|
||||
form,
|
||||
responsibilityCenters,
|
||||
billEdit,
|
||||
billid
|
||||
billEdit
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
||||
@@ -126,7 +124,7 @@ export function BillEnterModalLinesComponent({
|
||||
]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Input.TextArea disabled={disabled} autoSize />
|
||||
formInput: () => <Input.TextArea disabled={disabled} autoSize />
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.quantity"),
|
||||
@@ -158,7 +156,7 @@ export function BillEnterModalLinesComponent({
|
||||
]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <InputNumber precision={0} min={1} disabled={disabled} />
|
||||
formInput: () => <InputNumber precision={0} min={1} disabled={disabled} />
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.actual_price"),
|
||||
@@ -188,7 +186,7 @@ export function BillEnterModalLinesComponent({
|
||||
if (idx === index) {
|
||||
return {
|
||||
...item,
|
||||
actual_cost: !!item.actual_cost
|
||||
actual_cost: item.actual_cost
|
||||
? item.actual_cost
|
||||
: Math.round((parseFloat(e.target.value) * (1 - discount) + Number.EPSILON) * 100) / 100
|
||||
};
|
||||
@@ -258,7 +256,7 @@ export function BillEnterModalLinesComponent({
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => {
|
||||
const line = getFieldsValue(["billlines"]).billlines[index];
|
||||
if (!!!line) return null;
|
||||
if (!line) return null;
|
||||
let lineDiscount = 1 - line.actual_cost / line.actual_price;
|
||||
if (isNaN(lineDiscount)) lineDiscount = 0;
|
||||
return (
|
||||
@@ -322,7 +320,7 @@ export function BillEnterModalLinesComponent({
|
||||
]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => (
|
||||
formInput: () => (
|
||||
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
|
||||
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
? CiecaSelect(true, false)
|
||||
@@ -344,7 +342,7 @@ export function BillEnterModalLinesComponent({
|
||||
name: [field.name, "location"]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => (
|
||||
formInput: () => (
|
||||
<Select disabled={disabled}>
|
||||
{bodyshop.md_parts_locations.map((loc, idx) => (
|
||||
<Select.Option key={idx} value={loc}>
|
||||
@@ -366,7 +364,7 @@ export function BillEnterModalLinesComponent({
|
||||
name: [field.name, "deductedfromlbr"]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
formInput: () => <Switch disabled={disabled} />,
|
||||
additional: (record, index) => (
|
||||
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
||||
{() => {
|
||||
@@ -478,7 +476,7 @@ export function BillEnterModalLinesComponent({
|
||||
name: [field.name, "applicable_taxes", "federal"]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />
|
||||
formInput: () => <Switch disabled={disabled} />
|
||||
}
|
||||
]
|
||||
}),
|
||||
@@ -495,7 +493,7 @@ export function BillEnterModalLinesComponent({
|
||||
name: [field.name, "applicable_taxes", "state"]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />
|
||||
formInput: () => <Switch disabled={disabled} />
|
||||
},
|
||||
|
||||
...InstanceRenderManager({
|
||||
@@ -513,7 +511,7 @@ export function BillEnterModalLinesComponent({
|
||||
name: [field.name, "applicable_taxes", "local"]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />
|
||||
formInput: () => <Switch disabled={disabled} />
|
||||
}
|
||||
]
|
||||
}),
|
||||
@@ -575,7 +573,7 @@ export function BillEnterModalLinesComponent({
|
||||
}
|
||||
]}
|
||||
>
|
||||
{(fields, { add, remove, move }) => {
|
||||
{(fields, { add, remove }) => {
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
@@ -612,19 +610,7 @@ export function BillEnterModalLinesComponent({
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BillEnterModalLinesComponent);
|
||||
|
||||
const EditableCell = ({
|
||||
dataIndex,
|
||||
title,
|
||||
inputType,
|
||||
record,
|
||||
index,
|
||||
children,
|
||||
formInput,
|
||||
formItemProps,
|
||||
additional,
|
||||
wrapper,
|
||||
...restProps
|
||||
}) => {
|
||||
const EditableCell = ({ dataIndex, record, children, formInput, formItemProps, additional, wrapper, ...restProps }) => {
|
||||
const propsFinal = formItemProps && formItemProps(record);
|
||||
if (propsFinal && "key" in propsFinal) {
|
||||
delete propsFinal.key;
|
||||
|
||||
@@ -9,10 +9,10 @@ export const CalculateBillTotal = (invoice) => {
|
||||
let stateTax = Dinero({ amount: 0 });
|
||||
let localTax = Dinero({ amount: 0 });
|
||||
|
||||
if (!!!billlines) return null;
|
||||
if (!billlines) return null;
|
||||
|
||||
billlines.forEach((i) => {
|
||||
if (!!i) {
|
||||
if (i) {
|
||||
const itemTotal = Dinero({
|
||||
amount: Math.round((i.actual_cost || 0) * 100)
|
||||
}).multiply(i.quantity || 1);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
||||
import "./bill-inventory-table.styles.scss";
|
||||
@@ -13,7 +13,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
billEnterModal: selectBillEnterModal
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BillInventoryTable);
|
||||
@@ -22,7 +22,7 @@ export function BillInventoryTable({ billEnterModal, bodyshop, form, billEdit, i
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (inventoryData && inventoryData.inventory) {
|
||||
if (inventoryData?.inventory) {
|
||||
form.setFieldsValue({
|
||||
inventory: billEnterModal.context.consumeinventoryid
|
||||
? inventoryData.inventory.map((i) => {
|
||||
@@ -47,7 +47,7 @@ export function BillInventoryTable({ billEnterModal, bodyshop, form, billEdit, i
|
||||
|
||||
return (
|
||||
<Form.List name="inventory">
|
||||
{(fields, { add, remove, move }) => {
|
||||
{(fields) => {
|
||||
return (
|
||||
<>
|
||||
<Typography.Title level={4}>{t("inventory.labels.inventory")}</Typography.Title>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid var(--table-border-color);
|
||||
|
||||
.ant-form-item {
|
||||
margin-bottom: 0px !important;
|
||||
@@ -14,6 +14,6 @@
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
background-color: var(--table-hover-bg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Select } from "antd";
|
||||
import React, { forwardRef } from "react";
|
||||
import { forwardRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InstanceRenderMgr from "../../utils/instanceRenderMgr";
|
||||
|
||||
@@ -43,7 +43,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
|
||||
label: (
|
||||
<div style={{ whiteSpace: 'normal', wordBreak: 'break-word' }}>
|
||||
<div style={{ whiteSpace: "normal", wordBreak: "break-word" }}>
|
||||
<span>
|
||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
import { Button } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
@@ -15,7 +15,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
authLevel: selectAuthLevel,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, Space } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
@@ -26,7 +26,7 @@ export default function BillPrintButton({ billid }) {
|
||||
null,
|
||||
notification
|
||||
);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
console.warn("Warning: Error generating a document.");
|
||||
}
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
import { Button } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
@@ -13,7 +13,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
authLevel: selectAuthLevel
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useMutation } from "@apollo/client";
|
||||
import { Button, Tooltip } from "antd";
|
||||
import { t } from "i18next";
|
||||
import dayjs from "./../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_INVENTORY_AND_CREDIT } from "../../graphql/inventory.queries";
|
||||
@@ -17,7 +17,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BilllineAddInventory);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Checkbox, Input, Space, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaTasks } from "react-icons/fa";
|
||||
import { connect } from "react-redux";
|
||||
@@ -109,6 +109,13 @@ export function BillsListTableComponent({
|
||||
key: "vendorname",
|
||||
sorter: (a, b) => alphaSort(a.vendor.name, b.vendor.name),
|
||||
sortOrder: state.sortedInfo.columnKey === "vendorname" && state.sortedInfo.order,
|
||||
filters: bills
|
||||
? [...new Set(bills.map((bill) => bill.vendor.name))].map((name) => ({
|
||||
text: name,
|
||||
value: name
|
||||
}))
|
||||
: [],
|
||||
onFilter: (value, record) => record.vendor.name === value,
|
||||
render: (text, record) => <span>{record.vendor.name}</span>
|
||||
},
|
||||
{
|
||||
@@ -209,6 +216,7 @@ export function BillsListTableComponent({
|
||||
}
|
||||
});
|
||||
}}
|
||||
id="reconcile-bills-button"
|
||||
>
|
||||
<LockerWrapperComponent featureName="bills"> {t("jobs.actions.reconcile")}</LockerWrapperComponent>
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import queryString from "query-string";
|
||||
@@ -100,9 +100,9 @@ export default function BillsVendorsList() {
|
||||
selectedRowKeys: [search.vendorid],
|
||||
type: "radio"
|
||||
}}
|
||||
onRow={(record, rowIndex) => {
|
||||
onRow={(record) => {
|
||||
return {
|
||||
onClick: (event) => {
|
||||
onClick: () => {
|
||||
handleOnRowClick(record);
|
||||
} // click row
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { HomeFilled } from "@ant-design/icons";
|
||||
import { Breadcrumb, Col, Row } from "antd";
|
||||
import React from "react";
|
||||
import { selectBreadcrumbs, selectIsPartsEntry } from "../../redux/application/application.selectors";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import GlobalSearch from "../global-search/global-search.component";
|
||||
import GlobalSearchOs from "../global-search/global-search-os.component";
|
||||
@@ -13,18 +12,19 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
breadcrumbs: selectBreadcrumbs,
|
||||
bodyshop: selectBodyshop
|
||||
bodyshop: selectBodyshop,
|
||||
isPartsEntry: selectIsPartsEntry
|
||||
});
|
||||
|
||||
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
||||
export function BreadCrumbs({ breadcrumbs, bodyshop, isPartsEntry }) {
|
||||
const {
|
||||
treatments: { OpenSearch }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["OpenSearch"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
splitKey: bodyshop?.imexshopid
|
||||
});
|
||||
// TODO - Client Update - Technically key is not doing anything here
|
||||
|
||||
return (
|
||||
<Row className="breadcrumb-container">
|
||||
<Col xs={24} sm={24} md={16}>
|
||||
@@ -34,8 +34,8 @@ export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
||||
{
|
||||
key: "home",
|
||||
title: (
|
||||
<Link to={`/manage/`}>
|
||||
<HomeFilled /> {(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) || ""}
|
||||
<Link to={isPartsEntry ? `/parts/` : `/manage/`}>
|
||||
<HomeFilled /> {(bodyshop?.shopname && `(${bodyshop.shopname})`) || ""}
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, Form, Modal } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -32,12 +32,13 @@ export function ContractsFindModalContainer({ caBcEtfTableModal, toggleModalVisi
|
||||
logImEXEvent("ca_bc_etf_table_parse");
|
||||
setLoading(true);
|
||||
const claimNumbers = [];
|
||||
values.table.split("\n").forEach((row, idx, arr) => {
|
||||
values.table.split("\n").forEach((row) => {
|
||||
const { 1: claim, 2: shortclaim, 4: amount } = row.split("\t");
|
||||
if (!claim || !shortclaim) return;
|
||||
const trimmedShortClaim = shortclaim.trim();
|
||||
// const trimmedClaim = claim.trim();
|
||||
if (amount.slice(-1) === "-") {
|
||||
// NO OP
|
||||
}
|
||||
|
||||
claimNumbers.push({
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import { Form, Input, Radio } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
export default connect(mapStateToProps, null)(PartsReceiveModalComponent);
|
||||
|
||||
export function PartsReceiveModalComponent({ bodyshop, form }) {
|
||||
export function PartsReceiveModalComponent() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { CalculatorFilled } from "@ant-design/icons";
|
||||
import { Button, Form, InputNumber, Popover, Space } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { 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);
|
||||
|
||||
@@ -39,7 +40,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover destroyTooltipOnHide content={popContent} open={visibility} disabled={disabled}>
|
||||
<Popover destroyOnHidden content={popContent} open={visibility} disabled={disabled}>
|
||||
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||
<CalculatorFilled />
|
||||
</Button>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { CopyFilled, DeleteFilled } from "@ant-design/icons";
|
||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||
import { Button, Card, Col, Form, Input, message, Row, Space, Spin, Statistic } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -133,7 +133,6 @@ const CardPaymentModalComponent = ({
|
||||
});
|
||||
|
||||
if (window.intellipay) {
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(response.data);
|
||||
pollForIntelliPay(() => {
|
||||
SetIntellipayCallbackFunctions();
|
||||
@@ -149,7 +148,7 @@ const CardPaymentModalComponent = ({
|
||||
window.intellipay.initialize();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("job_payments.notifications.error.openingip")
|
||||
@@ -187,7 +186,7 @@ const CardPaymentModalComponent = ({
|
||||
message.success(t("general.actions.copied"));
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("job_payments.notifications.error.openingip")
|
||||
@@ -359,7 +358,7 @@ function pollForIntelliPay(callbackFunction) {
|
||||
const startTime = Date.now();
|
||||
|
||||
function checkFixAmount() {
|
||||
if (window.intellipay && window.intellipay.fixAmount !== undefined) {
|
||||
if (window.intellipay?.fixAmount) {
|
||||
callbackFunction();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
import { Button, Modal } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CardPaymentModalComponent from "./card-payment-modal.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
cardPaymentModal: selectCardPayment,
|
||||
bodyshop: selectBodyshop
|
||||
cardPaymentModal: selectCardPayment
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
||||
});
|
||||
|
||||
function CardPaymentModalContainer({ cardPaymentModal, toggleModalVisible, bodyshop }) {
|
||||
function CardPaymentModalContainer({ cardPaymentModal, toggleModalVisible }) {
|
||||
const { open } = cardPaymentModal;
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
@@ -34,16 +34,14 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||
|
||||
SubscribeToTopicForFCMNotification();
|
||||
|
||||
//Register WS handlers
|
||||
// Register WebSocket handlers
|
||||
if (socket && socket.connected) {
|
||||
registerMessagingHandlers({ socket, client });
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (socket && socket.connected) {
|
||||
return () => {
|
||||
unregisterMessagingHandlers({ socket });
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}, [bodyshop, socket, t, client]);
|
||||
|
||||
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
|
||||
|
||||
@@ -57,7 +57,7 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
existingConversation: true
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
logLocal("handleNewMessageSummary - Cache miss", { conversationId });
|
||||
}
|
||||
}
|
||||
@@ -202,8 +202,6 @@ 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 });
|
||||
@@ -211,7 +209,7 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
}
|
||||
}
|
||||
|
||||
return messageRef; // Keep other messages unchanged
|
||||
return messageRef;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -245,11 +243,8 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
});
|
||||
|
||||
const updatedList = existingList?.conversations
|
||||
? [
|
||||
newConversation,
|
||||
...existingList.conversations.filter((conv) => conv.id !== newConversation.id) // Prevent duplicates
|
||||
]
|
||||
: [newConversation];
|
||||
? [newConversation, ...existingList.conversations.filter((conv) => conv.id !== newConversation.id)]
|
||||
: [newConversation]; // Prevent duplicates
|
||||
|
||||
client.cache.writeQuery({
|
||||
query: CONVERSATION_LIST_QUERY,
|
||||
@@ -333,8 +328,7 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
}
|
||||
break;
|
||||
|
||||
case "tag-added":
|
||||
// Ensure `job_conversations` is properly formatted
|
||||
case "tag-added": { // Ensure `job_conversations` is properly formatted
|
||||
const formattedJobConversations = job_conversations.map((jc) => ({
|
||||
__typename: "job_conversations",
|
||||
jobid: jc.jobid || jc.job?.id,
|
||||
@@ -380,6 +374,7 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "tag-removed":
|
||||
try {
|
||||
@@ -403,6 +398,7 @@ export const registerMessagingHandlers = ({ socket, client }) => {
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
logLocal("handleConversationChanged - Unhandled type", { type });
|
||||
client.cache.modify({
|
||||
@@ -419,10 +415,95 @@ 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 }) => {
|
||||
@@ -431,4 +512,6 @@ 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,5 +1,5 @@
|
||||
import { Badge, Card, List, Space, Tag } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Badge, Card, List, Space, Tag, Tooltip } from "antd";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -9,44 +9,65 @@ 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";
|
||||
import { phone } from "phone";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedConversation: selectSelectedConversation
|
||||
selectedConversation: selectSelectedConversation,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setSelectedConversation: (conversationId) => dispatch(setSelectedConversation(conversationId))
|
||||
});
|
||||
|
||||
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation }) {
|
||||
// That comma is there for a reason, do not remove it
|
||||
function ChatConversationListComponent({ conversationList, selectedConversation, setSelectedConversation, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [, forceUpdate] = useState(false);
|
||||
const phoneNumbers = conversationList.map((item) => phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, ""));
|
||||
const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUTS, {
|
||||
variables: {
|
||||
bodyshopid: bodyshop.id,
|
||||
phone_numbers: phoneNumbers
|
||||
},
|
||||
skip: !conversationList.length,
|
||||
fetchPolicy: "cache-and-network"
|
||||
});
|
||||
|
||||
const optOutMap = useMemo(() => {
|
||||
const map = new Map();
|
||||
optOutData?.phone_number_opt_out?.forEach((optOut) => {
|
||||
map.set(optOut.phone_number, true);
|
||||
});
|
||||
return map;
|
||||
}, [optOutData?.phone_number_opt_out]);
|
||||
|
||||
// Re-render every minute
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
forceUpdate((prev) => !prev); // Toggle state to trigger re-render
|
||||
}, 60000); // 1 minute in milliseconds
|
||||
|
||||
return () => clearInterval(interval); // Cleanup on unmount
|
||||
forceUpdate((prev) => !prev);
|
||||
}, 60000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
// Memoize the sorted conversation list
|
||||
const sortedConversationList = React.useMemo(() => {
|
||||
const sortedConversationList = useMemo(() => {
|
||||
return _.orderBy(conversationList, ["updated_at"], ["desc"]);
|
||||
}, [conversationList]);
|
||||
|
||||
const renderConversation = (index) => {
|
||||
const renderConversation = (index, t) => {
|
||||
const item = sortedConversationList[index];
|
||||
const normalizedPhone = phone(item.phone_num, "CA").phoneNumber.replace(/^\+1/, "");
|
||||
const hasOptOutEntry = optOutMap.has(normalizedPhone);
|
||||
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
||||
const cardContentLeft =
|
||||
item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => <Tag key={idx}>{j.job.ro_number}</Tag>)
|
||||
: null;
|
||||
|
||||
const names = <>{_.uniq(item.job_conversations.map((j, idx) => OwnerNameDisplayFunction(j.job)))}</>;
|
||||
|
||||
const names = <>{_.uniq(item.job_conversations.map((j) => OwnerNameDisplayFunction(j.job)))}</>;
|
||||
const cardTitle = (
|
||||
<>
|
||||
{item.label && <Tag color="blue">{item.label}</Tag>}
|
||||
@@ -59,13 +80,22 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const cardExtra = <Badge count={item.messages_aggregate.aggregate.count} />;
|
||||
|
||||
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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
const getCardStyle = () =>
|
||||
item.id === selectedConversation
|
||||
? { backgroundColor: "rgba(128, 128, 128, 0.2)" }
|
||||
: { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" };
|
||||
? { backgroundColor: "var(--card-selected-bg)" }
|
||||
: { backgroundColor: index % 2 === 0 ? "var(--card-stripe-even-bg)" : "var(--card-stripe-odd-bg)" };
|
||||
|
||||
return (
|
||||
<List.Item
|
||||
@@ -73,9 +103,25 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
className={`chat-list-item ${item.id === selectedConversation ? "chat-list-selected-conversation" : ""}`}
|
||||
>
|
||||
<Card style={getCardStyle()} bordered={false} size="small" extra={cardExtra} title={cardTitle}>
|
||||
<div style={{ display: "inline-block", width: "70%", textAlign: "left" }}>{cardContentLeft}</div>
|
||||
<div style={{ display: "inline-block", width: "30%", textAlign: "right" }}>{cardContentRight}</div>
|
||||
<Card style={getCardStyle()} variant={true} size="small" extra={cardExtra} title={cardTitle}>
|
||||
<div
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: "70%",
|
||||
textAlign: "left"
|
||||
}}
|
||||
>
|
||||
{cardContentLeft}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: "30%",
|
||||
textAlign: "right"
|
||||
}}
|
||||
>
|
||||
{cardContentRight}
|
||||
</div>
|
||||
</Card>
|
||||
</List.Item>
|
||||
);
|
||||
@@ -85,7 +131,7 @@ function ChatConversationListComponent({ conversationList, selectedConversation,
|
||||
<div className="chat-list-container">
|
||||
<Virtuoso
|
||||
data={sortedConversationList}
|
||||
itemContent={(index) => renderConversation(index)}
|
||||
itemContent={(index) => renderConversation(index, t)}
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
/* Add spacing and better alignment for items */
|
||||
.chat-list-item {
|
||||
padding: 0.5rem 0; /* Add spacing between list items */
|
||||
padding: 0.2rem 0; /* Add spacing between list items */
|
||||
|
||||
.ant-card {
|
||||
border-radius: 8px; /* Slight rounding for card edges */
|
||||
|
||||
@@ -21,7 +21,7 @@ export function ChatConversationTitleTags({ jobConversations, bodyshop }) {
|
||||
|
||||
const handleRemoveTag = async (jobId) => {
|
||||
const convId = jobConversations[0].conversationid;
|
||||
if (!!convId) {
|
||||
if (convId) {
|
||||
await removeJobConversation({
|
||||
variables: {
|
||||
conversationId: convId,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Space } from "antd";
|
||||
import React from "react";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
|
||||
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
|
||||
@@ -16,10 +15,10 @@ const mapDispatchToProps = () => ({});
|
||||
export function ChatConversationTitle({ conversation }) {
|
||||
return (
|
||||
<Space className="chat-title" wrap>
|
||||
<PhoneNumberFormatter>{conversation && conversation.phone_num}</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter>{conversation?.phone_num}</PhoneNumberFormatter>
|
||||
<ChatLabelComponent conversation={conversation} />
|
||||
<ChatPrintButton conversation={conversation} />
|
||||
<ChatConversationTitleTags jobConversations={(conversation && conversation.job_conversations) || []} />
|
||||
<ChatConversationTitleTags jobConversations={conversation?.job_conversations || []} />
|
||||
<ChatTagRoContainer conversation={conversation || []} />
|
||||
<ChatArchiveButton conversation={conversation} />
|
||||
</Space>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import ChatConversationTitle from "../chat-conversation-title/chat-conversation-title.component";
|
||||
import ChatMessageListComponent from "../chat-messages-list/chat-message-list.component";
|
||||
|
||||
@@ -58,6 +58,7 @@ function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
userid
|
||||
created_at
|
||||
read
|
||||
is_system
|
||||
}
|
||||
`,
|
||||
data: message
|
||||
|
||||
@@ -14,7 +14,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export function ChatLabel({ conversation, bodyshop }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -13,13 +13,14 @@ 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) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector);
|
||||
|
||||
export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, conversation }) {
|
||||
@@ -37,9 +38,8 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
jobId: conversation.job_conversations[0] && conversation.job_conversations[0].jobid
|
||||
jobId: conversation.job_conversations[0]?.jobid
|
||||
},
|
||||
|
||||
skip: !open || !conversation.job_conversations || conversation.job_conversations.length === 0
|
||||
});
|
||||
|
||||
@@ -56,25 +56,25 @@ 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>
|
||||
<div className="media-selector-content">
|
||||
{loading && <LoadingSpinner />}
|
||||
{error && <AlertComponent message={error.message} type="error" />}
|
||||
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
|
||||
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
|
||||
<div className="error-message">{t("messaging.labels.maxtenimages")}</div>
|
||||
) : null}
|
||||
|
||||
{Imgproxy.treatment === "on" ? (
|
||||
<>
|
||||
{!bodyshop.uselocalmediaserver && (
|
||||
<JobsDocumentImgproxyGalleryExternal
|
||||
jobId={conversation.job_conversations[0].jobid}
|
||||
jobId={conversation.job_conversations[0]?.jobid}
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
/>
|
||||
)}
|
||||
{bodyshop.uselocalmediaserver && open && (
|
||||
<JobDocumentsLocalGalleryExternal
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
jobId={conversation.job_conversations[0] && conversation.job_conversations[0].jobid}
|
||||
jobId={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] && conversation.job_conversations[0].jobid}
|
||||
jobId={conversation.job_conversations[0]?.jobid}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -100,12 +100,17 @@ export function ChatMediaSelector({ bodyshop, selectedMedia, setSelectedMedia, c
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
conversation.job_conversations.length === 0 ? <div>{t("messaging.errors.noattachedjobs")}</div> : content
|
||||
conversation.job_conversations.length === 0 ? (
|
||||
<div className="no-jobs-message">{t("messaging.errors.noattachedjobs")}</div>
|
||||
) : (
|
||||
content
|
||||
)
|
||||
}
|
||||
title={t("messaging.labels.selectmedia")}
|
||||
trigger="click"
|
||||
open={open}
|
||||
onOpenChange={handleVisibleChange}
|
||||
classNames={{ root: "media-selector-popover" }}
|
||||
>
|
||||
<Badge count={selectedMedia.filter((s) => s.isSelected).length}>
|
||||
<PictureFilled style={{ margin: "0 .5rem" }} />
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
.media-selector-popover {
|
||||
.ant-popover-inner-content {
|
||||
position: relative;
|
||||
max-width: 640px;
|
||||
max-height: 480px;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
background-color: var(--popover-bg);
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.media-selector-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--error-text);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.no-jobs-message {
|
||||
font-size: 14px;
|
||||
color: var(--no-jobs-text);
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Style images within gallery components */
|
||||
.media-selector-content img {
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
margin: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Grid layout for gallery components */
|
||||
.media-selector-content .ant-image,
|
||||
.media-selector-content .gallery-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 4px;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { renderMessage } from "./renderMessage";
|
||||
import "./chat-message-list.styles.scss";
|
||||
@@ -76,7 +76,7 @@ export default function ChatMessageListComponent({ messages }) {
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
data={messages}
|
||||
overscan={!!messages.reduce((acc, message) => acc + (message.image_path?.length || 0), 0) ? messages.length : 0}
|
||||
overscan={messages.reduce((acc, message) => acc + (message.image_path?.length || 0), 0) ? messages.length : 0}
|
||||
itemContent={(index) => renderMessage(messages, index)}
|
||||
followOutput={(isAtBottom) => handleScrollStateChange(isAtBottom)}
|
||||
initialTopMostItemIndex={messages.length - 1}
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.archive-button {
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.chat-title {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -37,17 +40,18 @@
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
.chat-send-message-button{
|
||||
|
||||
.chat-send-message-button {
|
||||
margin: 0.3rem;
|
||||
padding-left: 0.5rem;
|
||||
|
||||
}
|
||||
|
||||
.message-icon {
|
||||
position: absolute;
|
||||
bottom: 0.1rem;
|
||||
right: 0.3rem;
|
||||
margin: 0 0.1rem;
|
||||
color: whitesmoke;
|
||||
color: var(--message-icon-color);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
@@ -75,7 +79,7 @@
|
||||
|
||||
&:last-child:after {
|
||||
width: 10px;
|
||||
background: white;
|
||||
background: var(--message-mine-tail-bg);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
@@ -87,11 +91,11 @@
|
||||
|
||||
.message {
|
||||
margin-right: 20%;
|
||||
background-color: #eee;
|
||||
background-color: var(--message-yours-bg);
|
||||
|
||||
&:last-child:before {
|
||||
left: -7px;
|
||||
background: #eee;
|
||||
background: var(--message-yours-bg);
|
||||
border-bottom-right-radius: 15px;
|
||||
}
|
||||
|
||||
@@ -107,14 +111,14 @@
|
||||
align-items: flex-end;
|
||||
|
||||
.message {
|
||||
color: white;
|
||||
color: var(--message-mine-text);
|
||||
margin-left: 25%;
|
||||
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%);
|
||||
background: linear-gradient(to bottom, var(--message-mine-bg-start) 0%, var(--message-mine-bg-end) 100%);
|
||||
padding-bottom: 0.6rem;
|
||||
|
||||
&:last-child:before {
|
||||
right: -8px;
|
||||
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%);
|
||||
background: linear-gradient(to bottom, var(--message-mine-bg-start) 0%, var(--message-mine-bg-end) 100%);
|
||||
border-bottom-left-radius: 15px;
|
||||
}
|
||||
|
||||
@@ -125,6 +129,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
.system {
|
||||
align-items: center;
|
||||
margin: 0.5rem 10%;
|
||||
|
||||
.message {
|
||||
background-color: var(--system-message-bg);
|
||||
border-radius: 10px;
|
||||
padding: 0.5rem 1rem;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
color: var(--system-message-text);
|
||||
width: fit-content;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.system-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--system-label-text);
|
||||
margin-bottom: 0.2rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.system-date {
|
||||
font-size: 0.75rem;
|
||||
color: var(--system-label-text);
|
||||
margin-top: 0.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.virtuoso-container {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
|
||||
@@ -2,17 +2,29 @@ import Icon from "@ant-design/icons";
|
||||
import { Tooltip } from "antd";
|
||||
import i18n from "i18next";
|
||||
import dayjs from "../../utils/day";
|
||||
import { MdDone, MdDoneAll } from "react-icons/md";
|
||||
import { MdClose, 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={`${message.isoutbound ? "mine messages" : "yours messages"}`}>
|
||||
<div key={index} className={messageClass}>
|
||||
<div className="message msgmargin">
|
||||
<Tooltip title={DateTimeFormatter({ children: message.created_at })}>
|
||||
<Tooltip title={tooltipTitle}>
|
||||
<div>
|
||||
{isSystem && <span className="system-label">System</span>}
|
||||
{/* Render images if available */}
|
||||
{message.image && message.image_path?.length > 0 && (
|
||||
<div className="message-images">
|
||||
@@ -26,20 +38,31 @@ export const renderMessage = (messages, index) => {
|
||||
</div>
|
||||
)}
|
||||
{/* Render text if available */}
|
||||
{message.text && <div>{message.text}</div>}
|
||||
{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>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
{/* 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>
|
||||
)}
|
||||
{/* 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>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Outbound message metadata */}
|
||||
{message.isoutbound && (
|
||||
{/* Outbound message metadata for non-system messages */}
|
||||
{!isSystem && message.isoutbound && (
|
||||
<div style={{ fontSize: 10 }}>
|
||||
{i18n.t("messaging.labels.sentby", {
|
||||
by: message.userid,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { PlusCircleOutlined } from "@ant-design/icons";
|
||||
import { Dropdown } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setMessage } from "../../redux/messaging/messaging.actions";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
|
||||
import { Space, Spin } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
@@ -31,7 +31,7 @@ export function ChatPrintButton({ conversation }) {
|
||||
type,
|
||||
conversation.id,
|
||||
notification
|
||||
).catch((e) => {
|
||||
).catch(() => {
|
||||
console.warn("Something went wrong generating a document.");
|
||||
});
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
|
||||
import { Input, Spin } from "antd";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ExclamationCircleOutlined, LoadingOutlined, SendOutlined } from "@ant-design/icons";
|
||||
import { Alert, Input, Space, Spin, Tooltip } from "antd";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -10,6 +10,9 @@ import { selectIsSending, selectMessage } from "../../redux/messaging/messaging.
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component";
|
||||
import ChatPresetsComponent from "../chat-presets/chat-presets.component";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { phone } from "phone";
|
||||
import { GET_PHONE_NUMBER_OPT_OUT } from "../../graphql/phone-number-opt-out.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -25,16 +28,24 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) {
|
||||
const inputArea = useRef(null);
|
||||
const [selectedMedia, setSelectedMedia] = useState([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const normalizedPhone = phone(conversation.phone_num, "CA").phoneNumber.replace(/^\+1/, "");
|
||||
const { data: optOutData } = useQuery(GET_PHONE_NUMBER_OPT_OUT, {
|
||||
variables: { bodyshopid: bodyshop.id, phone_number: normalizedPhone },
|
||||
fetchPolicy: "cache-and-network"
|
||||
});
|
||||
|
||||
const isOptedOut = !!optOutData?.phone_number_opt_out?.[0];
|
||||
|
||||
useEffect(() => {
|
||||
inputArea.current.focus();
|
||||
}, [isSending, setMessage]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleEnter = () => {
|
||||
const selectedImages = selectedMedia.filter((i) => i.isSelected);
|
||||
if ((message === "" || !message) && selectedImages.length === 0) return;
|
||||
if (isOptedOut) return; // Prevent sending if phone number is opted out
|
||||
logImEXEvent("messaging_send_message");
|
||||
|
||||
if (selectedImages.length < 11) {
|
||||
@@ -44,7 +55,8 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
|
||||
messagingServiceSid: bodyshop.messagingservicesid,
|
||||
conversationid: conversation.id,
|
||||
selectedMedia: selectedImages,
|
||||
imexshopid: bodyshop.imexshopid
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
bodyshopid: bodyshop.id
|
||||
};
|
||||
sendMessage(newMessage);
|
||||
setSelectedMedia(
|
||||
@@ -56,47 +68,67 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="imex-flex-row" style={{ width: "100%" }}>
|
||||
<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}
|
||||
placeholder={t("messaging.labels.typeamessage")}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onPressEnter={(event) => {
|
||||
event.preventDefault();
|
||||
if (!!!event.shiftKey) handleEnter();
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<SendOutlined
|
||||
className="chat-send-message-button"
|
||||
// disabled={message === "" || !message}
|
||||
onClick={handleEnter}
|
||||
/>
|
||||
<Spin
|
||||
style={{ display: `${isSending ? "" : "none"}` }}
|
||||
indicator={
|
||||
<LoadingOutlined
|
||||
style={{
|
||||
fontSize: 24
|
||||
}}
|
||||
spin
|
||||
<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"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</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>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { CloseCircleOutlined, LoadingOutlined } from "@ant-design/icons";
|
||||
import { Empty, Select, Space } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Checkbox, Form } from "antd";
|
||||
import React from "react";
|
||||
|
||||
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
||||
const { name, label, required } = formItem;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import FormTypes from "./config-form-types";
|
||||
|
||||
export default function ConfirmFormComponents({ componentList, readOnly }) {
|
||||
@@ -7,7 +6,7 @@ export default function ConfirmFormComponents({ componentList, readOnly }) {
|
||||
{componentList.map((f, idx) => {
|
||||
const Comp = FormTypes[f.type];
|
||||
|
||||
if (!!Comp) {
|
||||
if (Comp) {
|
||||
return <Comp key={idx} formItem={f} readOnly={readOnly} />;
|
||||
} else {
|
||||
return <div key={idx}>Error</div>;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Form, Rate } from "antd";
|
||||
import React from "react";
|
||||
|
||||
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
||||
const { name, label, required } = formItem;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Form, Slider } from "antd";
|
||||
import React from "react";
|
||||
|
||||
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
||||
const { name, label, required, min, max } = formItem;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Form, Input } from "antd";
|
||||
import React from "react";
|
||||
|
||||
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
||||
const { name, label, required } = formItem;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Form, Input } from "antd";
|
||||
import React from "react";
|
||||
|
||||
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
|
||||
const { name, label, required, rows } = formItem;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Button, Result } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Card, Input, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
|
||||
@@ -114,9 +114,9 @@ export default function ContractsCarsComponent({ loading, data, selectedCarId, h
|
||||
type: "radio",
|
||||
selectedRowKeys: [selectedCarId]
|
||||
}}
|
||||
onRow={(record, rowIndex) => {
|
||||
onRow={(record) => {
|
||||
return {
|
||||
onClick: (event) => {
|
||||
onClick: () => {
|
||||
handleSelect(record);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { QUERY_AVAILABLE_CC } from "../../graphql/courtesy-car.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import ContractCarsComponent from "./contract-cars.component";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMutation } from "@apollo/client";
|
||||
import { Button, Form, InputNumber, Popover, Radio, Select, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -16,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = () => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
@@ -270,7 +270,7 @@ export function ContractConvertToRo({ bodyshop, currentUser, contract, disabled
|
||||
// awaitRefetchQueries: true,
|
||||
});
|
||||
|
||||
if (!!result.errors) {
|
||||
if (result.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.inserting", {
|
||||
message: JSON.stringify(result.errors)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Card } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useLazyQuery } from "@apollo/client";
|
||||
import { Button } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GET_JOB_FOR_CC_CONTRACT } from "../../graphql/jobs.queries";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { WarningFilled } from "@ant-design/icons";
|
||||
import { Form, Input, InputNumber, Space } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user