Compare commits
421 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bed87174d4 | ||
|
|
03ae7bb160 | ||
|
|
6e6c44f2b9 | ||
|
|
ae0bfad89a | ||
|
|
27de849be7 | ||
|
|
289a666b6d | ||
|
|
b8836c7ae1 | ||
|
|
eca31c5618 | ||
|
|
7fdbedefce | ||
|
|
7140b8d585 | ||
|
|
5eed8d9809 | ||
|
|
57fe5b4c46 | ||
|
|
f266ee1cfe | ||
|
|
9550de5131 | ||
|
|
1f76ff882c | ||
|
|
749f73a272 | ||
|
|
9c1774c417 | ||
|
|
26b3a43ce5 | ||
|
|
78678dd3dc | ||
|
|
9dc4546b2e | ||
|
|
1309d8ff65 | ||
|
|
95aa0e45a6 | ||
|
|
ce9a77efcf | ||
|
|
e9e1e820a7 | ||
|
|
b027a4e618 | ||
|
|
c7fc75aa5c | ||
|
|
98d2372daf | ||
|
|
bf51380167 | ||
|
|
1ec827097f | ||
|
|
89fabf85e1 | ||
|
|
ff7dd7d3ea | ||
|
|
8cc4f88fa7 | ||
|
|
2439755f9e | ||
|
|
7e6ab3a5ff | ||
|
|
763384f05f | ||
|
|
34f876f838 | ||
|
|
cba2da8da7 | ||
|
|
f3d8aa3438 | ||
|
|
2f3eccf3d8 | ||
|
|
2b3e64d607 | ||
|
|
05b20505bb | ||
|
|
bddeae945c | ||
|
|
5b267f03b9 | ||
|
|
357d916e0a | ||
|
|
6ed12ebe7d | ||
|
|
6703bc025d | ||
|
|
387dac6779 | ||
|
|
6f454dd4cb | ||
|
|
1440a60228 | ||
|
|
f2aa3960aa | ||
|
|
06508f3ad8 | ||
|
|
8d4195b596 | ||
|
|
9e190e7fb7 | ||
|
|
5cbf00b0c8 | ||
|
|
655aeb86fc | ||
|
|
225549275d | ||
|
|
f0717b8b36 | ||
|
|
78771ae750 | ||
|
|
0389908398 | ||
|
|
54bee763df | ||
|
|
1117a94930 | ||
|
|
5fbfb992c7 | ||
|
|
87b3b65f3e | ||
|
|
9970190909 | ||
|
|
8eee371a90 | ||
|
|
ba97b1efef | ||
|
|
8d8887c28e | ||
|
|
3b19432974 | ||
|
|
a14b2340b0 | ||
|
|
624f8e77cb | ||
|
|
fb624c817d | ||
|
|
c2b4b66ed1 | ||
|
|
ffec03ab6c | ||
|
|
552163d7b9 | ||
|
|
db1f59578c | ||
|
|
8ec5831ec5 | ||
|
|
0146ac5b7b | ||
|
|
a603e5c0b8 | ||
|
|
9aab47d8f8 | ||
|
|
f2f84e2da8 | ||
|
|
338906e288 | ||
|
|
542997b1a7 | ||
|
|
5fce548666 | ||
|
|
80322caad0 | ||
|
|
56472d24d9 | ||
|
|
db5dcc271d | ||
|
|
73ab02225e | ||
|
|
83a1b7690d | ||
|
|
c9e28b1ed2 | ||
|
|
c25c66d00f | ||
|
|
d319ab49d4 | ||
|
|
a069989ea7 | ||
|
|
8e3aa186cb | ||
|
|
01c55d6277 | ||
|
|
3438907d8d | ||
|
|
ae020b651e | ||
|
|
d22988df15 | ||
|
|
8136a56ad2 | ||
|
|
830f6c0eea | ||
|
|
4c1849289a | ||
|
|
c45a4780e3 | ||
|
|
d4adc4c1aa | ||
|
|
d9e71423f5 | ||
|
|
6cac0f9594 | ||
|
|
2ab4615642 | ||
|
|
dd5961d419 | ||
|
|
8190958ba3 | ||
|
|
77e009f316 | ||
|
|
2b2738a8d1 | ||
|
|
3d10c9da7f | ||
|
|
e82c77d119 | ||
|
|
855a78be05 | ||
|
|
a29e840797 | ||
|
|
1b30c1ab58 | ||
|
|
80f235f12e | ||
|
|
9b67148522 | ||
|
|
6b501e4619 | ||
|
|
42f1d6fa13 | ||
|
|
3f247a9227 | ||
|
|
63b914731b | ||
|
|
23f8f69bbe | ||
|
|
fc3ea2bdf8 | ||
|
|
96e970faf7 | ||
|
|
c133195607 | ||
|
|
d75ea2b1a6 | ||
|
|
ec0fd840e4 | ||
|
|
971a81fc27 | ||
|
|
19050d31f7 | ||
|
|
e605433379 | ||
|
|
b9ebb70b7a | ||
|
|
79ed6f2388 | ||
|
|
785449a986 | ||
|
|
0b7d469e0e | ||
|
|
a57156756e | ||
|
|
c4c30d98d4 | ||
|
|
d4e8803b13 | ||
|
|
1f2786ddec | ||
|
|
e90cda07e4 | ||
|
|
9793daa04c | ||
|
|
117ced8fe7 | ||
|
|
e7909205d1 | ||
|
|
18028a70ab | ||
|
|
eeb8d8d26f | ||
|
|
23659fc412 | ||
|
|
ba65057782 | ||
|
|
60a859cac8 | ||
|
|
0cfe26093c | ||
|
|
d085a9c7c9 | ||
|
|
ec518a0593 | ||
|
|
1cd64ab6f1 | ||
|
|
26836f662a | ||
|
|
111f280674 | ||
|
|
6e88faa9d8 | ||
|
|
1ca8b2a78d | ||
|
|
cd2a7cad7f | ||
|
|
ed16156957 | ||
|
|
8dc1f7e08f | ||
|
|
2d3c13c587 | ||
|
|
5486907639 | ||
|
|
9233cef23a | ||
|
|
c16eafe892 | ||
|
|
b479684fe4 | ||
|
|
4201f61548 | ||
|
|
d04fc76840 | ||
|
|
0f84adc752 | ||
|
|
fbefd80959 | ||
|
|
6a691b54c8 | ||
|
|
fc75717d32 | ||
|
|
1459c6e993 | ||
|
|
f50292f9bf | ||
|
|
5b81912bd3 | ||
|
|
3c98a94c38 | ||
|
|
1d98de6d4d | ||
|
|
0ce5d9063a | ||
|
|
3b84e1d6ec | ||
|
|
d62f6e2116 | ||
|
|
71a26cc4ac | ||
|
|
32441e9406 | ||
|
|
e6dade1206 | ||
|
|
43d34cae07 | ||
|
|
a72a7948fe | ||
|
|
a24f6639a1 | ||
|
|
b2a0af32e9 | ||
|
|
cc58d14d32 | ||
|
|
9ce419b949 | ||
|
|
5053816be7 | ||
|
|
30ca34ea93 | ||
|
|
68d1a404b3 | ||
|
|
85e82b85ea | ||
|
|
23467280b4 | ||
|
|
aedad1c48f | ||
|
|
05cc4dd188 | ||
|
|
ea6351ea06 | ||
|
|
87d3ceb408 | ||
|
|
d08dd2b506 | ||
|
|
8a047d14a1 | ||
|
|
e103772aa4 | ||
|
|
c332699dc8 | ||
|
|
25e6e61d10 | ||
|
|
cdcd6b636a | ||
|
|
7879591bcf | ||
|
|
7fc6556866 | ||
|
|
3f5489ce7e | ||
|
|
5a90854861 | ||
|
|
8347a8c098 | ||
|
|
2bf074d85a | ||
|
|
50d47cd679 | ||
|
|
3a4e06eaa2 | ||
|
|
4be71726d4 | ||
|
|
c78db7eb08 | ||
|
|
e4dc711481 | ||
|
|
5114138c67 | ||
|
|
68b8743002 | ||
|
|
8f312bfffb | ||
|
|
7e7e109cfe | ||
|
|
05e5545466 | ||
|
|
ddb0990645 | ||
|
|
04dec6d91c | ||
|
|
a883b817b0 | ||
|
|
5b00ded5f6 | ||
|
|
c5b19d8f22 | ||
|
|
b7423aebf6 | ||
|
|
ee70aeb952 | ||
|
|
74d95e7cbb | ||
|
|
f6f6fab5ba | ||
|
|
699ffc822a | ||
|
|
4e35f5402c | ||
|
|
9b997d0924 | ||
|
|
d705f8211e | ||
|
|
03761bbb2a | ||
|
|
4d0794e90e | ||
|
|
e615c4a55b | ||
|
|
51eb3423f3 | ||
|
|
f6318666d9 | ||
|
|
544d4b8136 | ||
|
|
edf4846d55 | ||
|
|
f3754de843 | ||
|
|
3d920ad151 | ||
|
|
575f056360 | ||
|
|
716d9affb5 | ||
|
|
b01dd52da2 | ||
|
|
c75fddc2c0 | ||
|
|
db0c16f31d | ||
|
|
b286ab2439 | ||
|
|
fa57828ebd | ||
|
|
8052767002 | ||
|
|
932f572fb5 | ||
|
|
328a64eb90 | ||
|
|
c661fce8f1 | ||
|
|
60d1396011 | ||
|
|
3b647dfd37 | ||
|
|
50fe588949 | ||
|
|
0ced053d21 | ||
|
|
b8cf4a4d75 | ||
|
|
ff72657a82 | ||
|
|
92a96fdae6 | ||
|
|
b1a96d55ad | ||
|
|
49657816c6 | ||
|
|
7094b6ffbf | ||
|
|
ed7c2574eb | ||
|
|
45a9e37342 | ||
|
|
9e6a458203 | ||
|
|
55a279a700 | ||
|
|
82e2e332cf | ||
|
|
103d7c2bb2 | ||
|
|
f5f0b75617 | ||
|
|
c163554c3f | ||
|
|
bd75f593c2 | ||
|
|
fbc1866363 | ||
|
|
6480f7f2aa | ||
|
|
4cb3a79429 | ||
|
|
ca521eaeba | ||
|
|
f287ba2dac | ||
|
|
ff46bbbb3f | ||
|
|
cafca35500 | ||
|
|
9803841617 | ||
|
|
4dffbfe6fa | ||
|
|
6e61159608 | ||
|
|
3c85de3e34 | ||
|
|
1b5cddd371 | ||
|
|
6a7005299a | ||
|
|
1a4c9faab1 | ||
|
|
bfbf34e11d | ||
|
|
646754732d | ||
|
|
439d9e7b74 | ||
|
|
464f7044f0 | ||
|
|
7cde2f64af | ||
|
|
f674fff930 | ||
|
|
efc1157653 | ||
|
|
0677712d6e | ||
|
|
2e106a5d07 | ||
|
|
da7b97042e | ||
|
|
f018a2b2a6 | ||
|
|
c3f7d7bad2 | ||
|
|
70d857bfec | ||
|
|
f3265901b6 | ||
|
|
7c8ac50426 | ||
|
|
8ad39fe855 | ||
|
|
13b6218c43 | ||
|
|
bece3278f4 | ||
|
|
4c0a1960ad | ||
|
|
47324422a6 | ||
|
|
3b1da6901d | ||
|
|
fc6ec54233 | ||
|
|
64928d0849 | ||
|
|
56a580b1e7 | ||
|
|
f7af3b407b | ||
|
|
9a0674f5d7 | ||
|
|
cc30ea658e | ||
|
|
59869def31 | ||
|
|
a5d3f2caf1 | ||
|
|
4ad87a522c | ||
|
|
453812222b | ||
|
|
145cf7cc93 | ||
|
|
c09e22ed96 | ||
|
|
cdb2d4d2d6 | ||
|
|
29f0031c1e | ||
|
|
e8099e130a | ||
|
|
1cbca1ddf0 | ||
|
|
eeed004fe2 | ||
|
|
5a180b86fb | ||
|
|
e3059b41ae | ||
|
|
1a5c71048c | ||
|
|
fc4e97c9b5 | ||
|
|
3cd3d7414d | ||
|
|
1bb2212e4a | ||
|
|
a088f27f1d | ||
|
|
0e9ad1258d | ||
|
|
2a33f462a3 | ||
|
|
cbc164dbeb | ||
|
|
0bfc7033a9 | ||
|
|
2ec0d90a58 | ||
|
|
6382fdf19c | ||
|
|
9287e6608d | ||
|
|
0fcee5b25e | ||
|
|
d221763064 | ||
|
|
b39a5b755e | ||
|
|
30cb4ef562 | ||
|
|
449330441a | ||
|
|
fcab5e6ef2 | ||
|
|
0212b837ea | ||
|
|
e7438a099e | ||
|
|
b3303e3c38 | ||
|
|
c69c86d193 | ||
|
|
73ec8b8a70 | ||
|
|
af09796df8 | ||
|
|
954504de8d | ||
|
|
0aba040338 | ||
|
|
c3bfe87674 | ||
|
|
9aa1279144 | ||
|
|
4e6c45b195 | ||
|
|
4fdb939bd2 | ||
|
|
062a1dcc72 | ||
|
|
7b420b1855 | ||
|
|
40f61bbc8f | ||
|
|
f5d821c394 | ||
|
|
3958ec9189 | ||
|
|
1e4f52e541 | ||
|
|
5cc5cb444e | ||
|
|
4acf0c59ca | ||
|
|
2858a5e871 | ||
|
|
24496d3ee1 | ||
|
|
0a5df69b12 | ||
|
|
80efea02c6 | ||
|
|
9f5c282b41 | ||
|
|
b2602c3385 | ||
|
|
0e584af424 | ||
|
|
cdc3de2a33 | ||
|
|
3bfa556b02 | ||
|
|
44cb7577e2 | ||
|
|
46d2b08477 | ||
|
|
0193ff9e65 | ||
|
|
fd9a51209f | ||
|
|
d0a7b87e04 | ||
|
|
799b24c90e | ||
|
|
3e1a8c87d1 | ||
|
|
c886d874de | ||
|
|
4dfb020089 | ||
|
|
bc6f05acbc | ||
|
|
2701bbd501 | ||
|
|
1f2040d97c | ||
|
|
43963a3e91 | ||
|
|
4287311adb | ||
|
|
d0e8589a76 | ||
|
|
c4bab72947 | ||
|
|
aa4b4998fa | ||
|
|
ed4566e00f | ||
|
|
5c2cdfe16c | ||
|
|
12c75357b5 | ||
|
|
d40f3ee45a | ||
|
|
96a0def846 | ||
|
|
1fd595d0de | ||
|
|
52cf4f3d1f | ||
|
|
4d9be1d232 | ||
|
|
fb2bc20b4f | ||
|
|
744593e96a | ||
|
|
1e9308be9b | ||
|
|
411605e121 | ||
|
|
1da8d6abb3 | ||
|
|
cdcef798df | ||
|
|
f7207a9f3f | ||
|
|
7a54b55bd4 | ||
|
|
991dfc2ad5 | ||
|
|
718c8291a8 | ||
|
|
f1e84c348b | ||
|
|
5f513a8bef | ||
|
|
4b96d5a707 | ||
|
|
220f3d4410 | ||
|
|
841f62bd84 | ||
|
|
91e2e7931b | ||
|
|
3c6faf8473 | ||
|
|
c994eaaa8e | ||
|
|
517d8f4163 | ||
|
|
9deb2964a5 | ||
|
|
9cf9f8b844 | ||
|
|
ad46ea74c0 | ||
|
|
2a28855e4b | ||
|
|
8d25f60097 | ||
|
|
982a51f16e | ||
|
|
68d02648d7 | ||
|
|
89d5b1cfe4 |
@@ -5,6 +5,7 @@ orbs:
|
||||
aws-s3: circleci/aws-s3@4.0.0
|
||||
aws-cli: circleci/aws-cli@4.0
|
||||
eb: circleci/aws-elastic-beanstalk@2.0.1
|
||||
jira: circleci/jira@2.1.0
|
||||
jobs:
|
||||
imex-api-deploy:
|
||||
docker:
|
||||
@@ -18,6 +19,12 @@ jobs:
|
||||
eb status --verbose
|
||||
eb deploy
|
||||
eb status
|
||||
- jira/notify:
|
||||
environment: Production (ImEX) - API
|
||||
environment_type: production
|
||||
job_type: deployment
|
||||
pipeline_id: << pipeline.id >>
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
imex-hasura-migrate:
|
||||
docker:
|
||||
@@ -33,11 +40,16 @@ jobs:
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||
hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
||||
hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
||||
hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
||||
|
||||
- jira/notify:
|
||||
environment: Production (ImEX) - Hasura
|
||||
environment_type: production
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
imex-app-build:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
@@ -62,6 +74,7 @@ jobs:
|
||||
to: "s3://imex-online-production/"
|
||||
arguments: "--exclude '*.map'"
|
||||
|
||||
|
||||
imex-app-beta-build:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
@@ -86,6 +99,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://imex-online-beta/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify:
|
||||
environment: Production (ImEX) - Front End
|
||||
environment_type: production
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
rome-api-deploy:
|
||||
docker:
|
||||
@@ -99,7 +118,12 @@ jobs:
|
||||
eb status --verbose
|
||||
eb deploy
|
||||
eb status
|
||||
|
||||
- jira/notify:
|
||||
environment: Production (Rome) - API
|
||||
environment_type: production
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
rome-hasura-migrate:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
@@ -114,11 +138,16 @@ jobs:
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
|
||||
- jira/notify:
|
||||
environment: Production (Rome) - Hasura
|
||||
environment_type: production
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
rome-app-build:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
@@ -143,6 +172,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://rome-online-production/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify:
|
||||
environment: Production (Rome) - Front End
|
||||
environment_type: production
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
promanager-app-build:
|
||||
docker:
|
||||
@@ -168,6 +203,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://promanager-production/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify:
|
||||
environment: Production (ProManager) - Front End
|
||||
environment_type: production
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
test-rome-hasura-migrate:
|
||||
docker:
|
||||
@@ -183,10 +224,18 @@ jobs:
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
sleep 5
|
||||
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
sleep 10
|
||||
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
- jira/notify:
|
||||
environment: Test (Rome) - Hasura
|
||||
environment_type: testing
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
test-rome-app-build:
|
||||
docker:
|
||||
@@ -212,6 +261,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://rome-online-test/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify:
|
||||
environment: Test (Rome) - Front End
|
||||
environment_type: testing
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
test-promanager-app-build:
|
||||
docker:
|
||||
@@ -237,6 +292,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://promanager-testing/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify:
|
||||
environment: Test (ProManager) - Front End
|
||||
environment_type: testing
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
test-hasura-migrate:
|
||||
docker:
|
||||
@@ -252,10 +313,18 @@ jobs:
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
||||
sleep 15
|
||||
hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
||||
sleep 30
|
||||
hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
||||
- jira/notify:
|
||||
environment: Test (ImEX) - Hasura
|
||||
environment_type: testing
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
imex-test-app-build:
|
||||
docker:
|
||||
@@ -302,7 +371,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://imex-online-test-beta/"
|
||||
arguments: "--exclude '*.map'"
|
||||
|
||||
- jira/notify:
|
||||
environment: Test (ImEX) - Front End
|
||||
environment_type: testing
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
admin-app-build:
|
||||
docker:
|
||||
@@ -353,7 +427,7 @@ workflows:
|
||||
secret: ${HASURA_PROD_SECRET}
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
only: master-AIO
|
||||
- rome-api-deploy:
|
||||
filters:
|
||||
branches:
|
||||
@@ -363,7 +437,7 @@ workflows:
|
||||
branches:
|
||||
only: master-AIO
|
||||
- rome-hasura-migrate:
|
||||
secret: ${HASURA_PROD_SECRET}
|
||||
secret: ${HASURA_ROME_PROD_SECRET}
|
||||
filters:
|
||||
branches:
|
||||
only: master-AIO
|
||||
|
||||
24
.dockerignore
Normal file
24
.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
||||
# Directories to exclude
|
||||
.circleci
|
||||
.idea
|
||||
.platform
|
||||
.vscode
|
||||
_reference
|
||||
client
|
||||
redis/dockerdata
|
||||
hasura
|
||||
node_modules
|
||||
# Files to exclude
|
||||
.ebignore
|
||||
.editorconfig
|
||||
.eslintrc.json
|
||||
.gitignore
|
||||
.prettierrc.js
|
||||
Dockerfile
|
||||
README.MD
|
||||
bodyshop_translations.babel
|
||||
docker-compose.yml
|
||||
ecosystem.config.js
|
||||
|
||||
# Optional: Exclude logs and temporary files
|
||||
*.log
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text eol=lf
|
||||
0
.localstack/.gitkeep
Normal file
0
.localstack/.gitkeep
Normal file
24
.platform/hooks/predeploy/00-install-fonts.sh
Normal file
24
.platform/hooks/predeploy/00-install-fonts.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install required packages
|
||||
dnf install -y fontconfig freetype
|
||||
|
||||
# Move to the /tmp directory for temporary download and extraction
|
||||
cd /tmp
|
||||
|
||||
# Download the Montserrat font zip file
|
||||
wget https://images.imex.online/fonts/montserrat.zip -O montserrat.zip
|
||||
|
||||
# Unzip the downloaded font file
|
||||
unzip montserrat.zip -d montserrat
|
||||
|
||||
# Move the font files to the system fonts directory
|
||||
mv montserrat/montserrat/*.ttf /usr/share/fonts
|
||||
|
||||
# Rebuild the font cache
|
||||
fc-cache -fv
|
||||
|
||||
# Clean up
|
||||
rm -rf /tmp/montserrat /tmp/montserrat.zip
|
||||
|
||||
echo "Montserrat fonts installed and cached successfully."
|
||||
@@ -1 +1,2 @@
|
||||
client_max_body_size 50M;
|
||||
client_max_body_size 50M;
|
||||
client_body_buffer_size 5M;
|
||||
|
||||
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -14,6 +14,21 @@
|
||||
"request": "launch",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceRoot}/client/src"
|
||||
},
|
||||
{
|
||||
"name": "Attach to Node.js in Docker",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"address": "localhost",
|
||||
"port": 9229,
|
||||
"localRoot": "${workspaceFolder}",
|
||||
"remoteRoot": "/app",
|
||||
"protocol": "inspector",
|
||||
"restart": true,
|
||||
"sourceMaps": true,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
30
.vscode/settings.json
vendored
30
.vscode/settings.json
vendored
@@ -8,5 +8,35 @@
|
||||
"pattern": "**/IMEX.xml",
|
||||
"systemId": "logs/IMEX.xsd"
|
||||
}
|
||||
],
|
||||
"cSpell.words": [
|
||||
"antd",
|
||||
"appointmentconfirmation",
|
||||
"appt",
|
||||
"autohouse",
|
||||
"autohouseid",
|
||||
"billlines",
|
||||
"bodyshop",
|
||||
"bodyshopid",
|
||||
"bodyshops",
|
||||
"CIECA",
|
||||
"claimscorp",
|
||||
"claimscorpid",
|
||||
"Dinero",
|
||||
"driveable",
|
||||
"IMEX",
|
||||
"imexshopid",
|
||||
"jobid",
|
||||
"joblines",
|
||||
"Kaizen",
|
||||
"labhrs",
|
||||
"larhrs",
|
||||
"mixdata",
|
||||
"ownr",
|
||||
"promanager",
|
||||
"shopname",
|
||||
"smartscheduling",
|
||||
"timetickets",
|
||||
"touchtime"
|
||||
]
|
||||
}
|
||||
|
||||
47
Dockerfile
Normal file
47
Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
||||
# Use Amazon Linux 2023 as the base image
|
||||
FROM amazonlinux:2023
|
||||
|
||||
# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager)
|
||||
RUN dnf install -y git \
|
||||
&& curl -sL https://rpm.nodesource.com/setup_20.x | bash - \
|
||||
&& dnf install -y nodejs \
|
||||
&& dnf clean all
|
||||
|
||||
|
||||
# Install dependencies required by node-canvas
|
||||
RUN dnf install -y \
|
||||
gcc \
|
||||
gcc-c++ \
|
||||
cairo-devel \
|
||||
pango-devel \
|
||||
libjpeg-turbo-devel \
|
||||
giflib-devel \
|
||||
libpng-devel \
|
||||
make \
|
||||
python3 \
|
||||
python3-pip \
|
||||
&& dnf clean all
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# This is because our test route uses a git commit hash
|
||||
RUN git config --global --add safe.directory /app
|
||||
|
||||
# Copy package.json and package-lock.json
|
||||
COPY package.json ./
|
||||
|
||||
# Install Nodemon
|
||||
RUN npm install -g nodemon
|
||||
|
||||
# Install dependencies
|
||||
RUN npm i --no-package-lock
|
||||
|
||||
# Copy the rest of your application code
|
||||
COPY . .
|
||||
|
||||
# Expose the port your app runs on (adjust if necessary)
|
||||
EXPOSE 4000 9229
|
||||
|
||||
# Start the application
|
||||
CMD ["nodemon", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"]
|
||||
64
_reference/Documents/dockerreadme.md
Normal file
64
_reference/Documents/dockerreadme.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Setting up External Networking and Static IP for WSL2 using Hyper-V
|
||||
|
||||
This guide will walk you through the steps to configure your WSL2 (Windows Subsystem for Linux) instance to use an external Hyper-V virtual switch, enabling it to connect directly to your local network. Additionally, you'll learn how to assign a static IP address to your WSL2 instance.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Windows 11**
|
||||
2. **Docker Desktop For Windows (Latest Version)
|
||||
|
||||
# Docker Setup
|
||||
Inside the root of the project exists the `docker-compose.yaml` file, you can simply run
|
||||
`docker-compose up` to launch the backend.
|
||||
|
||||
Things to note:
|
||||
- When installing NPM packages, you will need to rebuild the `node-app` container
|
||||
- Making changes to the server files will restart the `node-app`
|
||||
|
||||
# Local Stack
|
||||
- LocalStack Front end (Optional) - https://apps.microsoft.com/detail/9ntrnft9zws2?hl=en-us&gl=US
|
||||
- http://localhost:4566/_aws/ses will allow you to see emails sent
|
||||
|
||||
# Docker Commands
|
||||
|
||||
## General `docker-compose` Commands:
|
||||
1. Bring up the services, force a rebuild of all services, and do not use the cache: `docker-compose up --build --no-cache`
|
||||
2. Start Containers in Detached Mode: This will run the containers in the background (detached mode): `docker-compose up -d`
|
||||
3. Stop and Remove Containers: Stops and removes the containers gracefully: `docker-compose down`
|
||||
4. Stop containers without removing them: `docker-compose stop`
|
||||
5. Remove Containers, Volumes, and Networks: `docker-compose down --volumes`
|
||||
6. Force rebuild of containers: `docker-compose build --no-cache`
|
||||
7. View running Containers: `docker-compose ps`
|
||||
8. View a specific containers logs: `docker-compose logs <container-name>`
|
||||
9. Scale services (multiple instances of a service): `docker-compose up --scale <container-name>=<instances number> -d`
|
||||
10. Watch a specific containers logs in realtime with timestamps: `docker-compose logs -f --timestamps <container-name>`
|
||||
|
||||
## Volume Management Commands
|
||||
1. List Docker volumes: `docker volume ls`
|
||||
2. Remove Unused volumes `docker volume prune`
|
||||
3. Remove specific volumes `docker volume rm <volume-name>`
|
||||
4. Inspect a volume: `docker volume inspect <volume-name>`
|
||||
|
||||
## Container Image Management Commands:
|
||||
1. List running containers: `docker ps`
|
||||
2. List all containers: `docker os -a`
|
||||
3. Remove Stopped containers: `docker container prune`
|
||||
4. Remove a specific container: `docker container rm <container-name>`
|
||||
5. Remove a specific image: `docker rmi <image-name>:<version>`
|
||||
6. Remove all unused images: `docker image prune -a`
|
||||
|
||||
## Network Management Commands:
|
||||
1. List networks: `docker network ls`
|
||||
2. Inspect a specific network: `docker network inspect <network-name>`
|
||||
3. Remove a specific network: `docker network rm <network-name>`
|
||||
4. Remove unused networks: `docker network prune`
|
||||
|
||||
## Debugging and maintenance:
|
||||
1. Enter a Running container: `docker exec -it <container name> /bin/bash` (could also be `/bin/sh` or for example `redis-cli` on a redis node)
|
||||
2. View container resource usage: `docker stats`
|
||||
3. Check Disk space used by Docker: `docker system df`
|
||||
4. Remove all unused Data (Nuclear option): `docker system prune`
|
||||
|
||||
## Specific examples
|
||||
1. To simulate a Clean state, one should run `docker system prune` followed by `docker volume prune -a`
|
||||
2. You can run `docker-compose up` without the `-d` option, and you will get what is identical to the experience you were used to, this includes being able to control-c and bring the entire stack down
|
||||
1
_reference/localEmailViewer/.gitignore
vendored
Normal file
1
_reference/localEmailViewer/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
7
_reference/localEmailViewer/README.md
Normal file
7
_reference/localEmailViewer/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
This will connect to your dockers local stack session and render the email in HTML.
|
||||
|
||||
```shell
|
||||
node index.js
|
||||
```
|
||||
|
||||
http://localhost:3334
|
||||
116
_reference/localEmailViewer/index.js
Normal file
116
_reference/localEmailViewer/index.js
Normal file
@@ -0,0 +1,116 @@
|
||||
// index.js
|
||||
|
||||
import express from 'express';
|
||||
import fetch from 'node-fetch';
|
||||
import {simpleParser} from 'mailparser';
|
||||
|
||||
const app = express();
|
||||
const PORT = 3334;
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:4566/_aws/ses');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const data = await response.json();
|
||||
const messagesHtml = await parseMessages(data.messages);
|
||||
res.send(renderHtml(messagesHtml));
|
||||
} catch (error) {
|
||||
console.error('Error fetching messages:', error);
|
||||
res.status(500).send('Error fetching messages');
|
||||
}
|
||||
});
|
||||
|
||||
async function parseMessages(messages) {
|
||||
const parsedMessages = await Promise.all(
|
||||
messages.map(async (message, index) => {
|
||||
try {
|
||||
const parsed = await simpleParser(message.RawData);
|
||||
return `
|
||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: lightgray">
|
||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: white">
|
||||
<div class="mb-2">
|
||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">From:</span> ${message.Source}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Region:</span> ${message.Region}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
||||
</div>
|
||||
</div>
|
||||
<div class="prose">
|
||||
${parsed.html || parsed.textAsHtml || 'No HTML content available'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
console.error('Error parsing email:', error);
|
||||
return `
|
||||
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
|
||||
<div class="mb-2">
|
||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">From:</span> ${message.Source}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Region:</span> ${message.Region}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
||||
</div>
|
||||
<div class="text-red-500">
|
||||
Error parsing email content
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})
|
||||
);
|
||||
return parsedMessages.join('');
|
||||
}
|
||||
|
||||
function renderHtml(messagesHtml) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email Messages Viewer</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f4f6;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.prose {
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container bg-white shadow-lg rounded-lg p-6">
|
||||
<h1 class="text-2xl font-bold text-center mb-6">Email Messages Viewer</h1>
|
||||
<div id="messages-container">
|
||||
${messagesHtml}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on http://localhost:${PORT}`);
|
||||
});
|
||||
1214
_reference/localEmailViewer/package-lock.json
generated
Normal file
1214
_reference/localEmailViewer/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
_reference/localEmailViewer/package.json
Normal file
18
_reference/localEmailViewer/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "localemailviewer",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"express": "^4.21.1",
|
||||
"mailparser": "^3.7.1",
|
||||
"node-fetch": "^3.3.2"
|
||||
}
|
||||
}
|
||||
59
_reference/prHelper/index.html
Normal file
59
_reference/prHelper/index.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>IMEX IO Extractor</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.output-box {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
min-height: 40px;
|
||||
}
|
||||
.copy-button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>IMEX IO Extractor</h1>
|
||||
<textarea id="inputText" placeholder="Paste your text here..."></textarea>
|
||||
<br>
|
||||
<button onclick="extractIO()">Extract</button>
|
||||
|
||||
<div class="output-box" id="outputBox" contenteditable="true"></div>
|
||||
<button class="copy-button" onclick="copyToClipboard()">Copy to Clipboard</button>
|
||||
|
||||
<script>
|
||||
function extractIO() {
|
||||
const inputText = document.getElementById('inputText').value;
|
||||
const ioNumbers = [...new Set(inputText.match(/IO-\d{4}/g))] // Extract unique IO-#### matches
|
||||
.map(io => ({ io, num: parseInt(io.split('-')[1]) })) // Extract number part for sorting
|
||||
.sort((a, b) => a.num - b.num) // Sort by the number
|
||||
.map(item => item.io); // Extract sorted IO-####
|
||||
|
||||
document.getElementById('outputBox').innerText = ioNumbers.join(', '); // Display horizontally
|
||||
}
|
||||
|
||||
function copyToClipboard() {
|
||||
const outputBox = document.getElementById('outputBox');
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(outputBox);
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
20
certs/cert.pem
Normal file
20
certs/cert.pem
Normal file
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWzCCAkOgAwIBAgIUD/QBSAXy/AlJ/cS4DaPWJLpChxgwDQYJKoZIhvcNAQEL
|
||||
BQAwPTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSEwHwYDVQQKDBhJbnRlcm5l
|
||||
dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjQwOTA5MTU0MjA1WhcNMjUwOTA5MTU0MjA1
|
||||
WjA9MQswCQYDVQQGEwJDQTELMAkGA1UECAwCT04xITAfBgNVBAoMGEludGVybmV0
|
||||
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AKSd0l7NJCNBwvtPU+dVPQkteg0AfC3sGqRnZMQteCRVa2oIgC4NoF3A9BK/yHbF
|
||||
ZF25OnXTck5vzc8yb3v73ndfTD9ASKNoiaZE84/GFBsxqlKR8cs0qVwzuAsdijMv
|
||||
vlMPNlMRyE1Rb7nR6HXGkPXNyxgMko03NXPkvIje9zRudm0Lf8L4q/hPyPkS7Mrm
|
||||
/uQfAAJe+xFcupkEX2XY7r0x1C+z6E8lA1UcuhK3SHdW7CWYqp1vU5/dnnUiXwCa
|
||||
GiC6Y1bCJB0pDAVISzy3JUDdINZdiqGR+y8ho3pstChf2mp/76s3N9eG9KA/qaFK
|
||||
BrGk2PvCoZ8/Aj1aMsRYFHECAwEAAaNTMFEwHQYDVR0OBBYEFDLJ2fbWP4VUJgOp
|
||||
PSs+NGHcVgRmMB8GA1UdIwQYMBaAFDLJ2fbWP4VUJgOpPSs+NGHcVgRmMA8GA1Ud
|
||||
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABfv5ut/y03atq0NMB0jeDY4
|
||||
AvW4ukk0k1svyqxFZCw9o7m2lHb/IjmVrZG1Sj4JWrrSv0s02ccb26/t6vazNa5L
|
||||
Powe3eyfHgfjTZJmgs8hyeMwKS0wWk/SPuu9JDhIJakiquqD+UVBGkHpP+XYvhDv
|
||||
vhS2XRlW+aEjpUmr1oCyyrc6WbzrYRNadqEsn/AxwcMyUbht3Ugjkg+OpidcTIQp
|
||||
5lv63waKo6I1vQofzBQ3L7JYsKo8kC0vAP7wkLxvzBii335uZJzzpFYFVOyVNezi
|
||||
dJdazPbRYbXz4LjltdEn/SNfRuKX8ZRiN2OSo7OfSrZaMTS87SfCSFJGgQM8Yrk=
|
||||
-----END CERTIFICATE-----
|
||||
27
certs/id_rsa
Normal file
27
certs/id_rsa
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAQEAvNl5fuVmLNv72BZNxnTqX5CHf5Xi8UxjYaYxHITSCx7blnhpVYLd
|
||||
qXvcOWXzbsfjch/den73QiW4n2FYz75oGMhUGlOYzdWKA9I9Sj09Qy1R06RhwDiZGd5qaM
|
||||
swEeXpkNmi2u4Qd2kJeDfUQUigjC09V81O/vrniGtQAJScfiG/itdm+Ufn09Z4MYk0HWjq
|
||||
iDokNEskoEPsibYIrb+Q6vdtuPkZO+wU/smXhPtgw5ST6oQdmm/gVNsRg5XNzxrire+z1G
|
||||
WatnnVL3hPnnfpnf8W589dyms7GGJwhPerSGTN1bn0T4+9C69Cd7LBJtxiuFdRmdlGLLLP
|
||||
RR48Rur71wAAA9AEfVsdBH1bHQAAAAdzc2gtcnNhAAABAQC82Xl+5WYs2/vYFk3GdOpfkI
|
||||
d/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN
|
||||
1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1
|
||||
AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DD
|
||||
lJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3Vuf
|
||||
RPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvXAAAAAwEAAQAAAQAQTosSLQbMmtY9S3e9
|
||||
yjyusdExcCTfhyQRu4MEHmfws+JsNMuLqbgwOVTD1AzYJQR7x0qdmDcLjCxL/uDnV16vvS
|
||||
Sd/Vf1dhnryIyoS29tzI0DRG94ZKq7tBvmHp1w/jRT4KcSVnovhW9e5Rs74+SRFhr06PKI
|
||||
S+wQOIv48Nwue9+QUMsMCpWgKXHx7SHNTHvnAfqdhi9O29SWlMA+v+mELZ5Cl+HU0UTt2I
|
||||
A1BxOe1N8FjN7KE2viJexsl3is1PuqMkpLl/wyHBJTVzUadl6DRALJQIm7/YO5goE72YOV
|
||||
Lpo27do3zjhC87dlKdATvZUzfKV0LuUVdxq/PNDZMUbBAAAAgQDShAqDZiDrdTUaGXfUVm
|
||||
QzcnVNbh2/KgZh4uux9QNHST562W6cnN7qxoRwVrM4BCOk1Kl73QQZW4nDvXX3PVC5j038
|
||||
8AXkcBHS9j9f4h72ue7D2jqlbHFa7aGU9zYgk9mbBF+GX3tDntkAIQjLtwOLfj1iiJ/clX
|
||||
mHFUAY1V4L8AAAAIEA3E4t/v0yU5D9AOI0r17UNYqfeyDoKAEDR4QbbFjO1l0kLnEJy7Zx
|
||||
Mhj18GilYg2y0P0v8dSM/oWXS8Hua2t5i9Exlv6gHhGlQ80mwYcVGIxewZ/pPeCPw0U+kt
|
||||
EKUjt09m9Oe7+6xHQsTBj9hY8/vqPmQwRalZFcLdhHiDiVKTcAAACBANtykaPXdVzEFx7D
|
||||
UOlsjVL7zM0EVOFXf9JJQ6BhazhmsEI2PYt3IpgGMo8cXkoUofAOIYjf421AabN1BqSO5J
|
||||
XTMxM0ZV3JmLLi804Mu9h1iFrVTBdLYOMJdc2VCo1EwHWpo9SXOyjxce/znvcIOU04aZhu
|
||||
TaPg816X+E+gw5JhAAAAFGRhdmVARGF2ZVJpY2hlci1JTUVYAQIDBAUG
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
certs/id_rsa.pub
Normal file
1
certs/id_rsa.pub
Normal file
@@ -0,0 +1 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC82Xl+5WYs2/vYFk3GdOpfkId/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DDlJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3VufRPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvX dave@DaveRicher-IMEX
|
||||
28
certs/key.pem
Normal file
28
certs/key.pem
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkndJezSQjQcL7
|
||||
T1PnVT0JLXoNAHwt7BqkZ2TELXgkVWtqCIAuDaBdwPQSv8h2xWRduTp103JOb83P
|
||||
Mm97+953X0w/QEijaImmRPOPxhQbMapSkfHLNKlcM7gLHYozL75TDzZTEchNUW+5
|
||||
0eh1xpD1zcsYDJKNNzVz5LyI3vc0bnZtC3/C+Kv4T8j5EuzK5v7kHwACXvsRXLqZ
|
||||
BF9l2O69MdQvs+hPJQNVHLoSt0h3VuwlmKqdb1Of3Z51Il8AmhogumNWwiQdKQwF
|
||||
SEs8tyVA3SDWXYqhkfsvIaN6bLQoX9pqf++rNzfXhvSgP6mhSgaxpNj7wqGfPwI9
|
||||
WjLEWBRxAgMBAAECggEAUNpHYlLFxh9dokujPUMreF+Cy/IKDBAkQc2au5RNpyLh
|
||||
YDIOqw/8TTAhcTgLQPLQygvZP9f8E7RsVLFD+pSJ/v2qmIJ9au1Edor1Sg+S/oxV
|
||||
SLrwFMunx2aLpcH7iAqSI3+cQg7A3+D4zD7iOz6tIl3Su9wo+v073tFhHKTOrEwv
|
||||
Qgr9Jf3viIiKV1ym+uQEVQndocfsj46FnFpXTQ2qs7kAF6FgAOLDGfQQwzkiqEBD
|
||||
NsqsDmbYIx6foZL+DEz1ZVO2M5B+xxpbNK82KwuQilVpimW8ui4LZHCe+RIFzt9+
|
||||
BK6KGlLpSEwTFliivI3nahy18JzskZsfyah0CPZlQQKBgQDVv+A0qIPGvOP3Sx+9
|
||||
HyeQCV23SkvvSvw8p8pMB0gvwv63YdJ7N/rJzBGS6YUHFWWZZgEeTgkJ6VJvoe0r
|
||||
8JL1el9uSUa7f0eayjmFBOGuzpktNVdIn2Tg7A9MWA4JqPNNC69RMOh86ewGD4J3
|
||||
a8Hz2a1bHxAmy/AZt2ukypY6eQKBgQDFJ7kqeOPkRBz9WbALRgVIXo8YWf5di0sQ
|
||||
r0HC03GAISHQ725A2IFBPHJWeqj0jaMiIZD0y+Obgp7KAskrJaLfsd7Ug775kFfw
|
||||
oUI9UAl6kRuPKvm3BaVAm46SQm+56VsgxTi73YN0NUp75THHZgAJjepF9zSpVJxq
|
||||
VY9DjEGruQKBgQCQCpGIatcCol/tUg69X7VFd0pULhkl1J5OMbQ9r9qRdRI5eg5h
|
||||
QsQaIQ7mtb8TmvOwf/DY/zVQHI+U8sXlCmW+TwzoQTENQSR7xzMj1LpRFqBaustr
|
||||
AR72A537kItFLzll/i3SxOam5uxK2UDOQSuerF4KPdCglGXkrpo3nt3F4QKBgQCa
|
||||
RArPAOjQo7tLQfJN3+wiRFsTYtd1uphx5bA/EdOtvj8HjVFnzADXWsTchf3N3UXY
|
||||
XwtdgGwIMpys1KEz8a8P+c2x26SDAj7NOmDqOMYx8Xju/WGHpBM6Cn30U6e4gK+d
|
||||
ZLSPyzQgqdIuP5hDvbwpvbGiLVw3Ys1BJtGCuSxpgQJ/eHnRiuSi5Zq5jGg+GpA+
|
||||
FEEc9NCy772rR+4uzEOqyIwqewffqzSuVWuIsY/8MP3fh+NDxl/mU6cB5QVeD54Z
|
||||
JZUKwmpM26muiM6WvVnM4ExPdSGA2+l4pZjby/KKd6F/w0tgZ1jb9Pb2/0vN3qVA
|
||||
Y4U4XNTMt2fxUACqiL4SHA==
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,5 +1,5 @@
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GA_CODE=231099835
|
||||
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||
@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=IMEX
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GA_CODE=231099835
|
||||
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||
@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_INSTANCE=PROMANAGER
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GA_CODE=231099835
|
||||
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
||||
VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
VITE_APP_COUNTRY=USA
|
||||
|
||||
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
/dev-dist
|
||||
|
||||
@@ -12,6 +12,6 @@ module.exports = defineConfig({
|
||||
setupNodeEvents(on, config) {
|
||||
return require("./cypress/plugins/index.js")(on, config);
|
||||
},
|
||||
baseUrl: "http://localhost:3000"
|
||||
baseUrl: "https://localhost:3000"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||
<link rel="icon" href="/favicon.png"/>
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||
@@ -14,7 +17,7 @@
|
||||
<meta name="theme-color" content="#1690ff"/>
|
||||
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
||||
<!-- TODO:AIo Update the individual logos for each.-->
|
||||
<link rel="apple-touch-icon" href="public/logo192.png"/>
|
||||
<link rel="apple-touch-icon" href="/logo192.png"/>
|
||||
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
@@ -46,77 +49,23 @@
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||
<meta name="description" content="Rome Online"/>
|
||||
<title>Rome Online</title>
|
||||
|
||||
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
|
||||
|
||||
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001
|
||||
party="LiveChat528346"></call-us-selector>
|
||||
|
||||
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
|
||||
|
||||
<!--<call-us
|
||||
|
||||
phonesystem-url=https://rometech.east.3cx.us:5001
|
||||
|
||||
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
|
||||
|
||||
id="wp-live-chat-by-3CX"
|
||||
|
||||
minimized="true"
|
||||
|
||||
animation-style="noanimation"
|
||||
|
||||
party="LiveChat528346"
|
||||
|
||||
minimized-style="bubbleright"
|
||||
|
||||
allow-call="true"
|
||||
|
||||
allow-video="false"
|
||||
|
||||
allow-soundnotifications="true"
|
||||
|
||||
enable-mute="true"
|
||||
|
||||
enable-onmobile="true"
|
||||
|
||||
offline-enabled="true"
|
||||
|
||||
enable="true"
|
||||
|
||||
ignore-queueownership="false"
|
||||
|
||||
authentication="both"
|
||||
|
||||
show-operator-actual-name="true"
|
||||
|
||||
aknowledge-received="true"
|
||||
|
||||
gdpr-enabled="false"
|
||||
|
||||
message-userinfo-format="name"
|
||||
|
||||
message-dateformat="both"
|
||||
|
||||
lang="browser"
|
||||
|
||||
button-icon-type="default"
|
||||
|
||||
greeting-visibility="none"
|
||||
|
||||
greeting-offline-visibility="none"
|
||||
|
||||
chat-delay="2000"
|
||||
|
||||
enable-direct-call="true"
|
||||
|
||||
enable-ga="false"
|
||||
|
||||
></call-us>-->
|
||||
|
||||
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js
|
||||
id="tcx-callus-js" charset="utf-8"></script>
|
||||
|
||||
<script type="text/javascript" id="zsiqchat">
|
||||
var $zoho = $zoho || {};
|
||||
$zoho.salesiq = $zoho.salesiq || {
|
||||
widgetcode: "siq01bb8ac617280bdacddfeb528f07734dadc64ef3f05efef9f769c1ec171af666",
|
||||
values: {},
|
||||
ready: function () {
|
||||
}
|
||||
};
|
||||
var d = document;
|
||||
s = d.createElement("script");
|
||||
s.type = "text/javascript";
|
||||
s.id = "zsiqscript";
|
||||
s.defer = true;
|
||||
s.src = "https://salesiq.zohopublic.com/widget";
|
||||
t = d.getElementsByTagName("script")[0];
|
||||
t.parentNode.insertBefore(s, t);
|
||||
</script>
|
||||
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||
<title>ProManager</title>
|
||||
|
||||
2128
client/package-lock.json
generated
2128
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,37 +9,37 @@
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@ant-design/pro-layout": "^7.19.12",
|
||||
"@apollo/client": "^3.11.4",
|
||||
"@emotion/is-prop-valid": "^1.3.0",
|
||||
"@fingerprintjs/fingerprintjs": "^4.4.3",
|
||||
"@apollo/client": "^3.11.8",
|
||||
"@emotion/is-prop-valid": "^1.3.1",
|
||||
"@fingerprintjs/fingerprintjs": "^4.5.0",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"@sentry/cli": "^2.33.1",
|
||||
"@sentry/cli": "^2.36.2",
|
||||
"@sentry/react": "^7.114.0",
|
||||
"@splitsoftware/splitio-react": "^1.12.1",
|
||||
"@splitsoftware/splitio-react": "^1.13.0",
|
||||
"@tanem/react-nprogress": "^5.0.51",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"antd": "^5.20.1",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^3.3.0",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.7.4",
|
||||
"axios": "^1.7.7",
|
||||
"classnames": "^2.5.1",
|
||||
"css-box-model": "^1.2.1",
|
||||
"dayjs": "^1.11.12",
|
||||
"dayjs": "^1.11.13",
|
||||
"dayjs-business-days2": "^1.2.2",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^10.12.5",
|
||||
"firebase": "^10.13.2",
|
||||
"graphql": "^16.9.0",
|
||||
"i18next": "^23.12.3",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"libphonenumber-js": "^1.11.5",
|
||||
"libphonenumber-js": "^1.11.9",
|
||||
"logrocket": "^8.1.2",
|
||||
"markerjs2": "^2.32.1",
|
||||
"markerjs2": "^2.32.2",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-url": "^8.0.1",
|
||||
"object-hash": "^3.0.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"query-string": "^9.1.0",
|
||||
"raf-schd": "^4.0.3",
|
||||
"react": "^18.3.1",
|
||||
"react-big-calendar": "^1.13.2",
|
||||
"react-big-calendar": "^1.14.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^7.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -58,15 +58,15 @@
|
||||
"react-icons": "^5.3.0",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-number-format": "^5.4.0",
|
||||
"react-number-format": "^5.4.2",
|
||||
"react-popopo": "^2.1.9",
|
||||
"react-product-fruits": "^2.2.6",
|
||||
"react-product-fruits": "^2.2.61",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-resizable": "^3.0.5",
|
||||
"react-router-dom": "^6.26.0",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-virtualized": "^9.22.5",
|
||||
"react-virtuoso": "^4.10.1",
|
||||
"react-virtuoso": "^4.10.4",
|
||||
"recharts": "^2.12.7",
|
||||
"redux": "^5.0.1",
|
||||
"redux-actions": "^3.0.3",
|
||||
@@ -74,22 +74,26 @@
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.1",
|
||||
"sass": "^1.77.8",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"styled-components": "^6.1.12",
|
||||
"sass": "^1.79.3",
|
||||
"socket.io-client": "^4.8.0",
|
||||
"styled-components": "^6.1.13",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"use-memo-one": "^1.1.3",
|
||||
"userpilot": "^1.3.5",
|
||||
"userpilot": "^1.3.6",
|
||||
"vite-plugin-ejs": "^1.7.0",
|
||||
"web-vitals": "^3.5.2"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "echo 'when updating react-big-calendar, remember to check to localizer in the calendar wrapper'",
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"start": "vite",
|
||||
"build": "dotenvx run --env-file=.env.development.imex -- vite build",
|
||||
"start:imex": "dotenvx run --env-file=.env.development.imex -- vite",
|
||||
"start:rome": "dotenvx run --env-file=.env.development.rome -- vite",
|
||||
"start:promanager": "dotenvx run --env-file=.env.development.promanager -- vite",
|
||||
"preview:imex": "dotenvx run --env-file=.env.development.imex -- vite preview",
|
||||
"preview:rome": "dotenvx run --env-file=.env.development.rome -- vite preview",
|
||||
"preview:promanager": "dotenvx run --env-file=.env.development.promanager -- vite preview",
|
||||
"build:test:imex": "env-cmd -f .env.test.imex npm run build",
|
||||
"build:test:rome": "env-cmd -f .env.test.rome npm run build",
|
||||
"build:test:promanager": "env-cmd -f .env.test.promanager npm run build",
|
||||
@@ -130,29 +134,30 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@dotenvx/dotenvx": "^1.7.0",
|
||||
"@dotenvx/dotenvx": "^1.14.1",
|
||||
"@emotion/babel-plugin": "^11.12.0",
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@sentry/webpack-plugin": "^2.22.2",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@sentry/webpack-plugin": "^2.22.4",
|
||||
"@testing-library/cypress": "^10.0.2",
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"chalk": "^5.3.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^13.13.3",
|
||||
"cypress": "^13.14.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"memfs": "^4.11.1",
|
||||
"memfs": "^4.12.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
"react-error-overlay": "6.0.11",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^5.4.0",
|
||||
"vite": "^5.4.7",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-pwa": "^0.20.1",
|
||||
"vite-plugin-style-import": "^2.0.0"
|
||||
"vite-plugin-pwa": "^0.20.5",
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"workbox-window": "^7.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import { ApolloProvider } from "@apollo/client";
|
||||
import { SplitFactoryProvider, SplitSdk } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import dayjs from "../utils/day";
|
||||
import "dayjs/locale/en";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
@@ -19,8 +17,6 @@ if (import.meta.env.DEV) {
|
||||
Userpilot.initialize("NX-69145f08");
|
||||
}
|
||||
|
||||
dayjs.locale("en");
|
||||
|
||||
const config = {
|
||||
core: {
|
||||
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
||||
|
||||
@@ -18,10 +18,10 @@ import { checkUserSession } from "../redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
||||
import PrivateRoute from "../components/PrivateRoute";
|
||||
import "./App.styles.scss";
|
||||
import handleBeta from "../utils/handleBeta";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||
import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||
@@ -108,8 +108,6 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
||||
}
|
||||
|
||||
handleBeta();
|
||||
|
||||
if (!online) {
|
||||
return (
|
||||
<Result
|
||||
@@ -204,7 +202,9 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
path="/manage/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
<SocketProvider bodyshop={bodyshop}>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
</SocketProvider>
|
||||
</ErrorBoundary>
|
||||
}
|
||||
>
|
||||
@@ -214,7 +214,9 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
path="/tech/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
<SocketProvider bodyshop={bodyshop}>
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
</SocketProvider>
|
||||
</ErrorBoundary>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { exportPageLimit } from "../../utils/config";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
|
||||
@@ -175,7 +175,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top", pageSize: pageLimit }}
|
||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { exportPageLimit } from "../../utils/config";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
@@ -177,7 +177,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top", pageSize: pageLimit }}
|
||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Button, Card, Input, Space, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { exportPageLimit } from "../../utils/config";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||
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";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import JobMarkSelectedExported from "../jobs-mark-selected-exported/jobs-mark-selected-exported";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
@@ -201,7 +201,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top" }}
|
||||
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -98,7 +98,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
});
|
||||
|
||||
billlines.forEach((billline) => {
|
||||
const { deductedfromlbr, inventories, jobline, ...il } = billline;
|
||||
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline;
|
||||
delete il.__typename;
|
||||
|
||||
if (il.id) {
|
||||
|
||||
@@ -7,10 +7,10 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CiecaSelect from "../../utils/Ciecaselect";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -72,7 +72,14 @@ export function BillEnterModalLinesComponent({
|
||||
<BillLineSearchSelect
|
||||
disabled={disabled}
|
||||
options={lineData}
|
||||
style={{ width: "100%", minWidth: "10rem" }}
|
||||
style={{
|
||||
width: "20rem",
|
||||
maxWidth: "20rem",
|
||||
minWidth: "10rem",
|
||||
whiteSpace: "normal",
|
||||
height: "auto",
|
||||
minHeight: "32px" // default height of Ant Design inputs
|
||||
}}
|
||||
allowRemoved={form.getFieldValue("is_credit_memo") || false}
|
||||
onSelect={(value, opt) => {
|
||||
setFieldsValue({
|
||||
@@ -105,7 +112,7 @@ export function BillEnterModalLinesComponent({
|
||||
title: t("billlines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
editable: true,
|
||||
|
||||
width: "20rem",
|
||||
formItemProps: (field) => {
|
||||
return {
|
||||
key: `${field.index}line_desc`,
|
||||
@@ -119,7 +126,7 @@ export function BillEnterModalLinesComponent({
|
||||
]
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Input disabled={disabled} />
|
||||
formInput: (record, index) => <Input.TextArea disabled={disabled} autoSize />
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.quantity"),
|
||||
|
||||
@@ -11,7 +11,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
showSearch
|
||||
popupMatchSelectWidth={false}
|
||||
popupMatchSelectWidth={true}
|
||||
optionLabelProp={"name"}
|
||||
// optionFilterProp="line_desc"
|
||||
filterOption={(inputValue, option) => {
|
||||
@@ -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' }}>
|
||||
<span>
|
||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
@@ -57,7 +57,7 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
|
||||
</span>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}))
|
||||
]}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { DeleteFilled, CopyFilled } from "@ant-design/icons";
|
||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, notification } from "antd";
|
||||
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, message, notification } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -14,10 +14,12 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||
import { getCurrentUser } from "../../firebase/firebase.utils";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
cardPaymentModal: selectCardPayment,
|
||||
bodyshop: selectBodyshop
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: getCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@@ -25,11 +27,17 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
||||
});
|
||||
|
||||
const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisible, insertAuditTrail }) => {
|
||||
const CardPaymentModalComponent = ({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
cardPaymentModal,
|
||||
toggleModalVisible,
|
||||
insertAuditTrail
|
||||
}) => {
|
||||
const { context, actions } = cardPaymentModal;
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [paymentLink, setPaymentLink] = useState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
// const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||
@@ -37,7 +45,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
|
||||
const [, { data, refetch, queryLoading }] = useLazyQuery(QUERY_RO_AND_OWNER_BY_JOB_PKS, {
|
||||
variables: { jobids: [context.jobid] },
|
||||
skip: true
|
||||
skip: !context?.jobid
|
||||
});
|
||||
|
||||
//Initialize the intellipay window.
|
||||
@@ -51,8 +59,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
//2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
|
||||
//Add a slight delay to allow the refetch to properly get the data.
|
||||
setTimeout(() => {
|
||||
if (actions && actions.refetch && typeof actions.refetch === "function")
|
||||
actions.refetch();
|
||||
if (actions && actions.refetch && typeof actions.refetch === "function") actions.refetch();
|
||||
setLoading(false);
|
||||
toggleModalVisible();
|
||||
}, 750);
|
||||
@@ -86,7 +93,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const handleIntelliPayCharge = async () => {
|
||||
setLoading(true);
|
||||
//Validate
|
||||
@@ -101,7 +107,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||
bodyshop,
|
||||
refresh: !!window.intellipay,
|
||||
paymentSplitMeta: form.getFieldsValue(),
|
||||
paymentSplitMeta: form.getFieldsValue()
|
||||
});
|
||||
|
||||
if (window.intellipay) {
|
||||
@@ -126,6 +132,42 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
}
|
||||
};
|
||||
|
||||
const handleIntelliPayChargeShortLink = async () => {
|
||||
setLoading(true);
|
||||
//Validate
|
||||
try {
|
||||
await form.validateFields();
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { payments } = form.getFieldsValue();
|
||||
const response = await axios.post("/intellipay/generate_payment_url", {
|
||||
bodyshop,
|
||||
amount: payments?.reduce((acc, val) => {
|
||||
return acc + (val?.amount || 0);
|
||||
}, 0),
|
||||
account: payments && data && data.jobs.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null,
|
||||
comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email })),
|
||||
paymentSplitMeta: form.getFieldsValue()
|
||||
});
|
||||
if (response.data) {
|
||||
setPaymentLink(response.data?.shorUrl);
|
||||
navigator.clipboard.writeText(response.data?.shorUrl);
|
||||
message.success(t("general.actions.copied"));
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("job_payments.notifications.error.openingip")
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title="Card Payment">
|
||||
<Spin spinning={loading}>
|
||||
@@ -202,16 +244,14 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
|
||||
<Form.Item
|
||||
shouldUpdate={(prevValues, curValues) =>
|
||||
prevValues.payments?.map((p) => p?.jobid).join() !== curValues.payments?.map((p) => p?.jobid).join()
|
||||
prevValues.payments?.map((p) => p?.jobid + p?.amount).join() !==
|
||||
curValues.payments?.map((p) => p?.jobid + p?.amount).join()
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
//If all of the job ids have been fileld in, then query and update the IP field.
|
||||
const { payments } = form.getFieldsValue();
|
||||
if (
|
||||
payments?.length > 0 &&
|
||||
payments?.filter((p) => p?.jobid).length === payments?.length
|
||||
) {
|
||||
if (payments?.length > 0 && payments?.filter((p) => p?.jobid).length === payments?.length) {
|
||||
refetch({ jobids: payments.map((p) => p.jobid) });
|
||||
}
|
||||
return (
|
||||
@@ -246,7 +286,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
const totalAmountToCharge = payments?.reduce((acc, val) => {
|
||||
return acc + (val?.amount || 0);
|
||||
}, 0);
|
||||
|
||||
return (
|
||||
<Space style={{ float: "right" }}>
|
||||
<Statistic title="Amount To Charge" value={totalAmountToCharge} precision={2} />
|
||||
@@ -273,11 +312,36 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
>
|
||||
{t("job_payments.buttons.proceedtopayment")}
|
||||
</Button>
|
||||
<Space direction="vertical" align="center">
|
||||
<Button
|
||||
type="primary"
|
||||
// data-ipayname="submit"
|
||||
className="ipayfield"
|
||||
loading={queryLoading || loading}
|
||||
disabled={!(totalAmountToCharge > 0)}
|
||||
onClick={handleIntelliPayChargeShortLink}
|
||||
>
|
||||
{t("job_payments.buttons.create_short_link")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{paymentLink && (
|
||||
<Space
|
||||
style={{ cursor: "pointer", float: "right" }}
|
||||
align="end"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(paymentLink);
|
||||
message.success(t("general.actions.copied"));
|
||||
}}
|
||||
>
|
||||
<div>{paymentLink}</div>
|
||||
<CopyFilled />
|
||||
</Space>
|
||||
)}
|
||||
</Spin>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Badge, Card, List, Space, Tag } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { AutoSizer, CellMeasurer, CellMeasurerCache, List as VirtualizedList } from "react-virtualized";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
|
||||
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||
@@ -25,12 +25,7 @@ function ChatConversationListComponent({
|
||||
setSelectedConversation,
|
||||
loadMoreConversations
|
||||
}) {
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 60
|
||||
});
|
||||
|
||||
const rowRenderer = ({ index, key, style, parent }) => {
|
||||
const renderConversation = (index) => {
|
||||
const item = conversationList[index];
|
||||
const cardContentRight = <TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
||||
const cardContentLeft =
|
||||
@@ -52,6 +47,7 @@ function ChatConversationListComponent({
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const cardExtra = <Badge count={item.messages_aggregate.aggregate.count || 0} />;
|
||||
|
||||
const getCardStyle = () =>
|
||||
@@ -60,40 +56,27 @@ function ChatConversationListComponent({
|
||||
: { backgroundColor: index % 2 === 0 ? "#f0f2f5" : "#ffffff" };
|
||||
|
||||
return (
|
||||
<CellMeasurer key={key} cache={cache} parent={parent} columnIndex={0} rowIndex={index}>
|
||||
<List.Item
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
style={style}
|
||||
className={`chat-list-item
|
||||
${item.id === selectedConversation ? "chat-list-selected-conversation" : null}`}
|
||||
>
|
||||
<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>
|
||||
</List.Item>
|
||||
</CellMeasurer>
|
||||
<List.Item
|
||||
key={item.id}
|
||||
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>
|
||||
</List.Item>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-list-container">
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<VirtualizedList
|
||||
height={height}
|
||||
width={width}
|
||||
rowCount={conversationList.length}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
|
||||
if (scrollTop + clientHeight === scrollHeight) {
|
||||
loadMoreConversations();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<Virtuoso
|
||||
data={conversationList}
|
||||
itemContent={(index) => renderConversation(index)}
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
endReached={loadMoreConversations} // Calls loadMoreConversations when scrolled to the bottom
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.chat-list-container {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
height: 100%; /* Ensure it takes up the full available height */
|
||||
border: 1px solid gainsboro;
|
||||
overflow: auto; /* Allow scrolling for the Virtuoso component */
|
||||
}
|
||||
|
||||
.chat-list-item {
|
||||
@@ -14,3 +14,24 @@
|
||||
color: #ff7a00;
|
||||
}
|
||||
}
|
||||
|
||||
/* Virtuoso item container adjustments */
|
||||
.chat-list-container > div {
|
||||
height: 100%; /* Ensure Virtuoso takes full height */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Add spacing and better alignment for items */
|
||||
.chat-list-item {
|
||||
padding: 0.5rem 0; /* Add spacing between list items */
|
||||
|
||||
.ant-card {
|
||||
border-radius: 8px; /* Slight rounding for card edges */
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); /* Subtle shadow for better definition */
|
||||
}
|
||||
|
||||
&:hover .ant-card {
|
||||
border-color: #ff7a00; /* Highlight border on hover */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,81 +1,83 @@
|
||||
import { useMutation, useQuery, useSubscription } from "@apollo/client";
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { CONVERSATION_SUBSCRIPTION_BY_PK, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
||||
import { MARK_MESSAGES_AS_READ_BY_CONVERSATION } from "../../graphql/messages.queries";
|
||||
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||
import ChatConversationComponent from "./chat-conversation.component";
|
||||
import axios from "axios";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ChatConversationComponent from "./chat-conversation.component";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedConversation: selectSelectedConversation,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(ChatConversationContainer);
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export function ChatConversationContainer({ bodyshop, selectedConversation }) {
|
||||
const {
|
||||
loading: convoLoading,
|
||||
error: convoError,
|
||||
data: convoData
|
||||
} = useQuery(GET_CONVERSATION_DETAILS, {
|
||||
variables: { conversationId: selectedConversation },
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
const { loading, error, data } = useSubscription(CONVERSATION_SUBSCRIPTION_BY_PK, {
|
||||
variables: { conversationId: selectedConversation }
|
||||
});
|
||||
|
||||
const { socket } = useContext(SocketContext);
|
||||
const [conversationDetails, setConversationDetails] = useState({});
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [markingAsReadInProgress, setMarkingAsReadInProgress] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error] = useState(null);
|
||||
|
||||
const [markConversationRead] = useMutation(MARK_MESSAGES_AS_READ_BY_CONVERSATION, {
|
||||
variables: { conversationId: selectedConversation },
|
||||
refetchQueries: ["UNREAD_CONVERSATION_COUNT"],
|
||||
update(cache) {
|
||||
cache.modify({
|
||||
id: cache.identify({
|
||||
__typename: "conversations",
|
||||
id: selectedConversation
|
||||
}),
|
||||
fields: {
|
||||
messages_aggregate(cached) {
|
||||
return { aggregate: { count: 0 } };
|
||||
}
|
||||
}
|
||||
// Fetch conversation details and messages when a conversation is selected
|
||||
useEffect(() => {
|
||||
if (socket && selectedConversation) {
|
||||
setLoading(true);
|
||||
socket.emit("join-conversation", selectedConversation);
|
||||
|
||||
socket.on("conversation-details", (data) => {
|
||||
setConversationDetails(data.conversation);
|
||||
setMessages(data.messages);
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
socket.on("new-message", (message) => {
|
||||
setMessages((prevMessages) => [...prevMessages, message]);
|
||||
});
|
||||
|
||||
socket.on("conversation-list-updated", (data) => {
|
||||
setConversationDetails(data.conversation);
|
||||
setMessages(data.messages);
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.emit("leave-conversation", selectedConversation);
|
||||
socket.off("conversation-details");
|
||||
socket.off("new-message");
|
||||
socket.off("conversation-list-updated");
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const unreadCount =
|
||||
data &&
|
||||
data.messages &&
|
||||
data.messages.reduce((acc, val) => {
|
||||
return !val.read && !val.isoutbound ? acc + 1 : acc;
|
||||
}, 0);
|
||||
}, [socket, selectedConversation]);
|
||||
|
||||
// Mark messages as read
|
||||
const handleMarkConversationAsRead = async () => {
|
||||
if (unreadCount > 0 && !!selectedConversation && !markingAsReadInProgress) {
|
||||
if (messages.some((msg) => !msg.read) && !markingAsReadInProgress) {
|
||||
setMarkingAsReadInProgress(true);
|
||||
await markConversationRead({});
|
||||
await axios.post("/sms/markConversationRead", {
|
||||
conversationid: selectedConversation,
|
||||
imexshopid: bodyshop.imexshopid
|
||||
// Emit a WebSocket event to mark messages as read
|
||||
socket.emit("mark-as-read", {
|
||||
conversationId: selectedConversation,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
bodyshopId: bodyshop.id
|
||||
});
|
||||
|
||||
setMarkingAsReadInProgress(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!messages || !conversationDetails || !messages.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatConversationComponent
|
||||
subState={[loading || convoLoading, error || convoError]}
|
||||
conversation={convoData ? convoData.conversations_by_pk : {}}
|
||||
messages={data ? data.messages : []}
|
||||
subState={[loading, error]}
|
||||
conversation={conversationDetails}
|
||||
messages={messages}
|
||||
handleMarkConversationAsRead={handleMarkConversationAsRead}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatConversationContainer);
|
||||
|
||||
@@ -2,105 +2,90 @@ import Icon from "@ant-design/icons";
|
||||
import { Tooltip } from "antd";
|
||||
import i18n from "i18next";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import { MdDone, MdDoneAll } from "react-icons/md";
|
||||
import { AutoSizer, CellMeasurer, CellMeasurerCache, List } from "react-virtualized";
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import "./chat-message-list.styles.scss";
|
||||
|
||||
export default function ChatMessageListComponent({ messages }) {
|
||||
const virtualizedListRef = useRef(null);
|
||||
const virtuosoRef = useRef(null);
|
||||
|
||||
const _cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
// minHeight: 50,
|
||||
defaultHeight: 100
|
||||
});
|
||||
// Scroll to the bottom after a short delay when the component mounts
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (virtuosoRef.current) {
|
||||
virtuosoRef.current.scrollToIndex({
|
||||
index: messages.length - 1,
|
||||
behavior: "auto" // Instantly scroll to the bottom
|
||||
});
|
||||
}
|
||||
}, 100); // Delay of 100ms to allow rendering
|
||||
return () => clearTimeout(timer); // Cleanup the timer on unmount
|
||||
}, [messages.length]); // Run only once on component mount
|
||||
|
||||
const scrollToBottom = (renderedrows) => {
|
||||
//console.log("Scrolling to", messages.length);
|
||||
// !!virtualizedListRef.current &&
|
||||
// virtualizedListRef.current.scrollToRow(messages.length);
|
||||
// Outstanding isue on virtualization: https://github.com/bvaughn/react-virtualized/issues/1179
|
||||
//Scrolling does not work on this version of React.
|
||||
};
|
||||
// Scroll to the bottom after the new messages are rendered
|
||||
useEffect(() => {
|
||||
if (virtuosoRef.current) {
|
||||
// Allow the DOM and Virtuoso to fully render the new data
|
||||
setTimeout(() => {
|
||||
virtuosoRef.current.scrollToIndex({
|
||||
index: messages.length - 1,
|
||||
align: "end", // Ensure the last message is fully visible
|
||||
behavior: "smooth" // Smooth scrolling
|
||||
});
|
||||
}, 50); // Slight delay to ensure layout recalculates
|
||||
}
|
||||
}, [messages]); // Triggered when new messages are added
|
||||
|
||||
useEffect(scrollToBottom, [messages]);
|
||||
|
||||
const _rowRenderer = ({ index, key, parent, style }) => {
|
||||
const renderMessage = (index) => {
|
||||
const message = messages[index];
|
||||
return (
|
||||
<CellMeasurer cache={_cache} key={key} rowIndex={index} parent={parent}>
|
||||
{({ measure, registerChild }) => (
|
||||
<div
|
||||
ref={registerChild}
|
||||
onLoad={measure}
|
||||
style={style}
|
||||
className={`${messages[index].isoutbound ? "mine messages" : "yours messages"}`}
|
||||
>
|
||||
<div className="message msgmargin">
|
||||
{MessageRender(messages[index])}
|
||||
{StatusRender(messages[index].status)}
|
||||
<div key={index} className={`${message.isoutbound ? "mine messages" : "yours messages"}`}>
|
||||
<div className="message msgmargin">
|
||||
<Tooltip title={DateTimeFormatter({ children: message.created_at })}>
|
||||
<div>
|
||||
{message.image_path &&
|
||||
message.image_path.map((i, idx) => (
|
||||
<div key={idx} style={{ display: "flex", justifyContent: "center" }}>
|
||||
<a href={i} target="__blank" rel="noopener noreferrer">
|
||||
<img alt="Received" className="message-img" src={i} />
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
<div>{message.text}</div>
|
||||
</div>
|
||||
{messages[index].isoutbound && (
|
||||
<div style={{ fontSize: 10 }}>
|
||||
{i18n.t("messaging.labels.sentby", {
|
||||
by: messages[index].userid,
|
||||
time: dayjs(messages[index].created_at).format("MM/DD/YYYY @ hh:mm a")
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
{message.status && (
|
||||
<div className="message-status">
|
||||
<Icon
|
||||
component={message.status === "sent" ? MdDone : message.status === "delivered" ? MdDoneAll : null}
|
||||
className="message-icon"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{message.isoutbound && (
|
||||
<div style={{ fontSize: 10 }}>
|
||||
{i18n.t("messaging.labels.sentby", {
|
||||
by: message.userid,
|
||||
time: dayjs(message.created_at).format("MM/DD/YYYY @ hh:mm a")
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</CellMeasurer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat">
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
ref={virtualizedListRef}
|
||||
width={width}
|
||||
height={height}
|
||||
rowHeight={_cache.rowHeight}
|
||||
rowRenderer={_rowRenderer}
|
||||
rowCount={messages.length}
|
||||
overscanRowCount={10}
|
||||
estimatedRowSize={150}
|
||||
scrollToIndex={messages.length}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<Virtuoso
|
||||
ref={virtuosoRef}
|
||||
data={messages}
|
||||
itemContent={(index) => renderMessage(index)}
|
||||
followOutput="smooth" // Ensure smooth scrolling when new data is appended
|
||||
style={{ height: "100%", width: "100%" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const MessageRender = (message) => {
|
||||
return (
|
||||
<Tooltip title={DateTimeFormatter({ children: message.created_at })}>
|
||||
<div>
|
||||
{message.image_path &&
|
||||
message.image_path.map((i, idx) => (
|
||||
<div key={idx} style={{ display: "flex", justifyContent: "center" }}>
|
||||
<a href={i} target="__blank">
|
||||
<img alt="Received" className="message-img" src={i} />
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
<div>{message.text}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const StatusRender = (status) => {
|
||||
switch (status) {
|
||||
case "sent":
|
||||
return <Icon component={MdDone} className="message-icon" />;
|
||||
case "delivered":
|
||||
return <Icon component={MdDoneAll} className="message-icon" />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,37 +1,30 @@
|
||||
.message-icon {
|
||||
//position: absolute;
|
||||
// bottom: 0rem;
|
||||
color: whitesmoke;
|
||||
border: #000000;
|
||||
position: absolute;
|
||||
margin: 0 0.1rem;
|
||||
bottom: 0.1rem;
|
||||
right: 0.3rem;
|
||||
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.chat {
|
||||
flex: 1;
|
||||
//width: 300px;
|
||||
//border: solid 1px #eee;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0.8rem 0rem;
|
||||
overflow: hidden; // Ensure the content scrolls correctly
|
||||
}
|
||||
|
||||
.messages {
|
||||
//margin-top: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5rem; // Add padding to avoid edge clipping
|
||||
}
|
||||
|
||||
.message {
|
||||
border-radius: 20px;
|
||||
padding: 0.25rem 0.8rem;
|
||||
//margin-top: 5px;
|
||||
// margin-bottom: 5px;
|
||||
//display: inline-block;
|
||||
|
||||
.message-img {
|
||||
max-width: 10rem;
|
||||
@@ -56,7 +49,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.yours .message.last:before {
|
||||
.yours .message:last-child:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
@@ -68,7 +61,7 @@
|
||||
border-bottom-right-radius: 15px;
|
||||
}
|
||||
|
||||
.yours .message.last:after {
|
||||
.yours .message:last-child:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
@@ -88,12 +81,11 @@
|
||||
color: white;
|
||||
margin-left: 25%;
|
||||
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%);
|
||||
background-attachment: fixed;
|
||||
position: relative;
|
||||
padding-bottom: 0.6rem;
|
||||
}
|
||||
|
||||
.mine .message.last:before {
|
||||
.mine .message:last-child:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
@@ -102,11 +94,10 @@
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background: linear-gradient(to bottom, #00d0ea 0%, #0085d1 100%);
|
||||
background-attachment: fixed;
|
||||
border-bottom-left-radius: 15px;
|
||||
}
|
||||
|
||||
.mine .message.last:after {
|
||||
.mine .message:last-child:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
|
||||
@@ -1,73 +1,59 @@
|
||||
import { InfoCircleOutlined, MessageOutlined, ShrinkOutlined, SyncOutlined } from "@ant-design/icons";
|
||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { InfoCircleOutlined, MessageOutlined, ShrinkOutlined } from "@ant-design/icons";
|
||||
import { Badge, Card, Col, Row, Space, Tooltip, Typography } from "antd";
|
||||
import React, { useCallback, useContext, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { CONVERSATION_LIST_QUERY, UNREAD_CONVERSATION_COUNT } from "../../graphql/conversations.queries";
|
||||
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
|
||||
import { selectChatVisible, selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||
import {
|
||||
selectChatVisible,
|
||||
selectConversations,
|
||||
selectSelectedConversation,
|
||||
selectUnreadCount
|
||||
} from "../../redux/messaging/messaging.selectors";
|
||||
import ChatConversationListComponent from "../chat-conversation-list/chat-conversation-list.component";
|
||||
import ChatConversationContainer from "../chat-conversation/chat-conversation.container";
|
||||
import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import "./chat-popup.styles.scss";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedConversation: selectSelectedConversation,
|
||||
chatVisible: selectChatVisible
|
||||
chatVisible: selectChatVisible,
|
||||
bodyshop: selectBodyshop,
|
||||
conversations: selectConversations,
|
||||
unreadCount: selectUnreadCount
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleChatVisible: () => dispatch(toggleChatVisible())
|
||||
});
|
||||
|
||||
export function ChatPopupComponent({ chatVisible, selectedConversation, toggleChatVisible }) {
|
||||
export function ChatPopupComponent({
|
||||
chatVisible,
|
||||
selectedConversation,
|
||||
toggleChatVisible,
|
||||
bodyshop,
|
||||
conversations,
|
||||
unreadCount
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [pollInterval, setpollInterval] = useState(0);
|
||||
|
||||
const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
...(pollInterval > 0 ? { pollInterval } : {})
|
||||
});
|
||||
|
||||
const [getConversations, { loading, data, refetch, fetchMore }] = useLazyQuery(CONVERSATION_LIST_QUERY, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !chatVisible,
|
||||
...(pollInterval > 0 ? { pollInterval } : {})
|
||||
});
|
||||
|
||||
const fcmToken = sessionStorage.getItem("fcmtoken");
|
||||
const { socket } = useContext(SocketContext);
|
||||
|
||||
// Emit event to open messaging when chat becomes visible
|
||||
useEffect(() => {
|
||||
if (fcmToken) {
|
||||
setpollInterval(0);
|
||||
} else {
|
||||
setpollInterval(60000);
|
||||
if (chatVisible && socket && bodyshop?.id) {
|
||||
socket.emit("open-messaging", bodyshop.id);
|
||||
}
|
||||
}, [fcmToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatVisible)
|
||||
getConversations({
|
||||
variables: {
|
||||
offset: 0
|
||||
}
|
||||
});
|
||||
}, [chatVisible, getConversations]);
|
||||
}, [chatVisible, socket, bodyshop?.id]);
|
||||
|
||||
// Handle loading more conversations
|
||||
const loadMoreConversations = useCallback(() => {
|
||||
if (data)
|
||||
fetchMore({
|
||||
variables: {
|
||||
offset: data.conversations.length
|
||||
}
|
||||
});
|
||||
}, [data, fetchMore]);
|
||||
|
||||
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
|
||||
if (socket) {
|
||||
socket.emit("load-more-conversations", { offset: conversations.length });
|
||||
}
|
||||
}, [socket, conversations.length]);
|
||||
|
||||
return (
|
||||
<Badge count={unreadCount}>
|
||||
@@ -80,21 +66,19 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
|
||||
<Tooltip title={t("messaging.labels.recentonly")}>
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
<SyncOutlined style={{ cursor: "pointer" }} onClick={() => refetch()} />
|
||||
{pollInterval > 0 && <Tag color="yellow">{t("messaging.labels.nopush")}</Tag>}
|
||||
<ShrinkOutlined
|
||||
onClick={() => toggleChatVisible()}
|
||||
style={{ position: "absolute", right: ".5rem", top: ".5rem" }}
|
||||
/>
|
||||
</Space>
|
||||
<ShrinkOutlined
|
||||
onClick={() => toggleChatVisible()}
|
||||
style={{ position: "absolute", right: ".5rem", top: ".5rem" }}
|
||||
/>
|
||||
|
||||
<Row gutter={[8, 8]} className="chat-popup-content">
|
||||
<Col span={8}>
|
||||
{loading ? (
|
||||
{conversations && conversations.length === 0 ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<ChatConversationListComponent
|
||||
conversationList={data ? data.conversations : []}
|
||||
conversationList={conversations.conversations}
|
||||
loadMoreConversations={loadMoreConversations}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,55 +1,66 @@
|
||||
import { LoadingOutlined, SendOutlined } from "@ant-design/icons";
|
||||
import { Input, Spin } from "antd";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { sendMessage, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { selectIsSending, selectMessage } from "../../redux/messaging/messaging.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import ChatMediaSelector from "../chat-media-selector/chat-media-selector.component";
|
||||
import ChatPresetsComponent from "../chat-presets/chat-presets.component";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
isSending: selectIsSending,
|
||||
message: selectMessage
|
||||
message: selectMessage,
|
||||
user: selectCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
sendMessage: (message) => dispatch(sendMessage(message)),
|
||||
setMessage: (message) => dispatch(setMessage(message))
|
||||
});
|
||||
|
||||
function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSending, message, setMessage }) {
|
||||
function ChatSendMessageComponent({ conversation, bodyshop, isSending, message, setMessage, user }) {
|
||||
const { socket } = useContext(SocketContext); // Access WebSocket instance
|
||||
const inputArea = useRef(null);
|
||||
const [selectedMedia, setSelectedMedia] = useState([]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
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;
|
||||
|
||||
logImEXEvent("messaging_send_message");
|
||||
|
||||
if (selectedImages.length < 11) {
|
||||
sendMessage({
|
||||
const messageData = {
|
||||
user,
|
||||
to: conversation.phone_num,
|
||||
body: message || "",
|
||||
messagingServiceSid: bodyshop.messagingservicesid,
|
||||
conversationid: conversation.id,
|
||||
selectedMedia: selectedImages,
|
||||
imexshopid: bodyshop.imexshopid
|
||||
});
|
||||
};
|
||||
|
||||
// Emit the send-message event via WebSocket
|
||||
socket.emit("send-message", messageData);
|
||||
|
||||
setSelectedMedia(
|
||||
selectedMedia.map((i) => {
|
||||
return { ...i, isSelected: false };
|
||||
})
|
||||
);
|
||||
|
||||
// Optionally clear the input message
|
||||
setMessage("");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,15 +85,11 @@ function ChatSendMessageComponent({ conversation, bodyshop, sendMessage, isSendi
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onPressEnter={(event) => {
|
||||
event.preventDefault();
|
||||
if (!!!event.shiftKey) handleEnter();
|
||||
if (!event.shiftKey) handleEnter();
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
<SendOutlined
|
||||
className="imex-flex-row__margin"
|
||||
// disabled={message === "" || !message}
|
||||
onClick={handleEnter}
|
||||
/>
|
||||
<SendOutlined className="imex-flex-row__margin" onClick={handleEnter} />
|
||||
<Spin
|
||||
style={{ display: `${isSending ? "" : "none"}` }}
|
||||
indicator={
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { WarningFilled } from "@ant-design/icons";
|
||||
import { Form, Input, InputNumber, Space } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import dayjs from "../../utils/day";
|
||||
//import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
|
||||
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
|
||||
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
|
||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import {
|
||||
default as DateTimePicker,
|
||||
default as FormDateTimePicker
|
||||
} from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
@@ -18,10 +20,10 @@ import ContractFormJobPrefill from "./contract-form-job-prefill.component";
|
||||
export default function ContractFormComponent({ form, create = false, selectedJobState, selectedCar }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<FormFieldsChanged form={form} />
|
||||
<>
|
||||
{!create && <FormFieldsChanged form={form} />}
|
||||
<LayoutFormRow>
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item
|
||||
label={t("contracts.fields.status")}
|
||||
name="status"
|
||||
@@ -50,7 +52,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<Form.Item label={t("contracts.fields.scheduledreturn")} name="scheduledreturn">
|
||||
<FormDateTimePicker />
|
||||
</Form.Item>
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item label={t("contracts.fields.actualreturn")} name="actualreturn">
|
||||
<FormDateTimePicker />
|
||||
</Form.Item>
|
||||
@@ -122,7 +124,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
}}
|
||||
</Form.Item>
|
||||
)}
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item label={t("contracts.fields.kmend")} name="kmend">
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
@@ -145,25 +147,21 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
>
|
||||
<CourtesyCarFuelSlider />
|
||||
</Form.Item>
|
||||
{create ? null : (
|
||||
{!create && (
|
||||
<Form.Item label={t("contracts.fields.fuelin")} name="fuelin" span={8}>
|
||||
<CourtesyCarFuelSlider />
|
||||
</Form.Item>
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
<div>
|
||||
<Space wrap>
|
||||
{selectedJobState && (
|
||||
<div>
|
||||
<ContractFormJobPrefill jobId={selectedJobState && selectedJobState[0]} form={form} />
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
//<ContractLicenseDecodeButton form={form} />
|
||||
}
|
||||
</Space>
|
||||
</div>
|
||||
<LayoutFormRow header={t("contracts.labels.driverinformation")}>
|
||||
<Space wrap>
|
||||
{create && selectedJobState && (
|
||||
<ContractFormJobPrefill jobId={selectedJobState && selectedJobState[0]} form={form} />
|
||||
)}
|
||||
{/* {<ContractLicenseDecodeButton form={form} />} */}
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow noDivider={true}>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.driver_dlnumber")}
|
||||
name="driver_dlnumber"
|
||||
@@ -183,9 +181,8 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
const dlExpiresBeforeReturn = dayjs(form.getFieldValue("driver_dlexpiry")).isBefore(
|
||||
dayjs(form.getFieldValue("scheduledreturn"))
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.driver_dlexpiry")}
|
||||
name="driver_dlexpiry"
|
||||
@@ -204,11 +201,10 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<span>{t("contracts.labels.dlexpirebeforereturn")}</span>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("contracts.fields.driver_dlst")} name="driver_dlst">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
@@ -315,6 +311,6 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Card, Table, Tag } from "antd";
|
||||
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import dayjs from "../../../utils/day";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import axios from "axios";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import dayjs from "../../../utils/day";
|
||||
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
const fortyFiveDaysAgo = () => dayjs().subtract(45, "day").toLocaleString();
|
||||
|
||||
@@ -46,6 +46,11 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
||||
dataIndex: "humanReadable",
|
||||
key: "humanReadable"
|
||||
},
|
||||
{
|
||||
title: t("job_lifecycle.columns.average_human_readable"),
|
||||
dataIndex: "averageHumanReadable",
|
||||
key: "averageHumanReadable"
|
||||
},
|
||||
{
|
||||
title: t("job_lifecycle.columns.status_count"),
|
||||
key: "statusCount",
|
||||
|
||||
@@ -40,13 +40,11 @@ export function DmsLogEvents({ socket, logs, bodyshop }) {
|
||||
|
||||
function LogLevelHierarchy(level) {
|
||||
switch (level) {
|
||||
case "TRACE":
|
||||
return "pink";
|
||||
case "DEBUG":
|
||||
return "orange";
|
||||
case "INFO":
|
||||
return "blue";
|
||||
case "WARNING":
|
||||
case "WARN":
|
||||
return "yellow";
|
||||
case "ERROR":
|
||||
return "red";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { acceptEula } from "../../redux/user/user.actions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
import "./eula.styles.scss";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
@@ -208,7 +208,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
||||
{
|
||||
required: true,
|
||||
validator: (_, value) => {
|
||||
if (day(value).isSame(day(), "day")) {
|
||||
if (dayjs(value).isSame(dayjs(), "day")) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(t("eula.messages.date_accepted")));
|
||||
|
||||
@@ -1,22 +1,40 @@
|
||||
import { DatePicker } from "antd";
|
||||
import { DatePicker, Space, TimePicker } from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
import dayjs from "../../utils/day";
|
||||
import { fuzzyMatchDate } from "./formats.js";
|
||||
|
||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const DateTimePicker = ({
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
id,
|
||||
onlyFuture,
|
||||
onlyToday,
|
||||
isDateOnly = false,
|
||||
isSeparatedTime = false,
|
||||
bodyshop,
|
||||
...restProps
|
||||
}) => {
|
||||
const [isManualInput, setIsManualInput] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newDate) => {
|
||||
if (onChange) {
|
||||
onChange(newDate || null);
|
||||
onChange(bodyshop?.timezone && newDate ? dayjs(newDate).tz(bodyshop.timezone, true) : newDate);
|
||||
}
|
||||
setIsManualInput(false);
|
||||
},
|
||||
[onChange]
|
||||
[onChange, bodyshop?.timezone]
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
@@ -70,24 +88,57 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, is
|
||||
|
||||
return (
|
||||
<div onKeyDown={handleKeyDown} id={id} style={{ width: "100%" }}>
|
||||
<DatePicker
|
||||
showTime={
|
||||
isDateOnly
|
||||
? false
|
||||
: {
|
||||
format: "hh:mm a",
|
||||
minuteStep: 15,
|
||||
defaultValue: dayjs(dayjs(), "HH:mm:ss")
|
||||
}
|
||||
}
|
||||
format={isDateOnly ? "MM/DD/YYYY" : "MM/DD/YYYY hh:mm a"}
|
||||
value={value ? dayjs(value) : null}
|
||||
onChange={handleChange}
|
||||
placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")}
|
||||
onBlur={onBlur || handleBlur}
|
||||
disabledDate={handleDisabledDate}
|
||||
{...restProps}
|
||||
/>
|
||||
{isSeparatedTime && (
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
<DatePicker
|
||||
showTime={false}
|
||||
format="MM/DD/YYYY"
|
||||
value={value ? dayjs(value) : null}
|
||||
onChange={handleChange}
|
||||
placeholder={t("general.labels.date")}
|
||||
onBlur={handleBlur}
|
||||
disabledDate={handleDisabledDate}
|
||||
isDateOnly={true}
|
||||
{...restProps}
|
||||
/>
|
||||
{value && (
|
||||
<TimePicker
|
||||
format="hh:mm a"
|
||||
minuteStep={15}
|
||||
defaultOpenValue={dayjs(value)
|
||||
.hour(dayjs().hour())
|
||||
.minute(Math.floor(dayjs().minute() / 15) * 15)
|
||||
.second(0)}
|
||||
onChange={(value) => {
|
||||
handleChange(value);
|
||||
onBlur();
|
||||
}}
|
||||
placeholder={t("general.labels.time")}
|
||||
{...restProps}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
)}
|
||||
{!isSeparatedTime && (
|
||||
<DatePicker
|
||||
showTime={
|
||||
isDateOnly
|
||||
? false
|
||||
: {
|
||||
format: "hh:mm a",
|
||||
minuteStep: 15,
|
||||
defaultValue: dayjs(dayjs(), "HH:mm:ss")
|
||||
}
|
||||
}
|
||||
format={isDateOnly ? "MM/DD/YYYY" : "MM/DD/YYYY hh:mm a"}
|
||||
value={value ? dayjs(value) : null}
|
||||
onChange={handleChange}
|
||||
placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")}
|
||||
onBlur={onBlur || handleBlur}
|
||||
disabledDate={handleDisabledDate}
|
||||
{...restProps}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -99,7 +150,8 @@ DateTimePicker.propTypes = {
|
||||
id: PropTypes.string,
|
||||
onlyFuture: PropTypes.bool,
|
||||
onlyToday: PropTypes.bool,
|
||||
isDateOnly: PropTypes.bool
|
||||
isDateOnly: PropTypes.bool,
|
||||
isSeparatedTime: PropTypes.bool
|
||||
};
|
||||
|
||||
export default React.memo(DateTimePicker);
|
||||
export default connect(mapStateToProps, null)(DateTimePicker);
|
||||
|
||||
@@ -3,13 +3,15 @@ import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
|
||||
export default function GlobalSearchOs() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(false);
|
||||
|
||||
@@ -177,7 +179,18 @@ export default function GlobalSearchOs() {
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoComplete options={data} onSearch={handleSearch} defaultActiveFirstOption onClear={() => setData([])}>
|
||||
<AutoComplete
|
||||
options={data}
|
||||
onSearch={handleSearch}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") return;
|
||||
const firstUrlForSearch = data?.[0]?.options?.[0]?.label?.props?.to;
|
||||
if (!firstUrlForSearch) return;
|
||||
navigate(firstUrlForSearch);
|
||||
}}
|
||||
defaultActiveFirstOption
|
||||
onClear={() => setData([])}
|
||||
>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AutoComplete, Divider, Input, Space } from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -13,6 +13,7 @@ import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.compon
|
||||
export default function GlobalSearch() {
|
||||
const { t } = useTranslation();
|
||||
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const executeSearch = (v) => {
|
||||
if (v && v.variables.search && v.variables.search !== "" && v.variables.search.length >= 3) callSearch(v);
|
||||
@@ -20,7 +21,6 @@ export default function GlobalSearch() {
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
console.log("Handle Search");
|
||||
debouncedExecuteSearch({ variables: { search: value } });
|
||||
};
|
||||
|
||||
@@ -156,7 +156,17 @@ export default function GlobalSearch() {
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<AutoComplete options={options} onSearch={handleSearch} defaultActiveFirstOption>
|
||||
<AutoComplete
|
||||
options={options}
|
||||
onSearch={handleSearch}
|
||||
defaultActiveFirstOption
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") return;
|
||||
const firstUrlForSearch = options?.[0]?.options?.[0]?.label?.props?.to;
|
||||
if (!firstUrlForSearch) return;
|
||||
navigate(firstUrlForSearch);
|
||||
}}
|
||||
>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
|
||||
@@ -13,7 +13,6 @@ import Icon, {
|
||||
FileFilled,
|
||||
HomeFilled,
|
||||
ImportOutlined,
|
||||
InfoCircleOutlined,
|
||||
LineChartOutlined,
|
||||
PaperClipOutlined,
|
||||
PhoneOutlined,
|
||||
@@ -27,8 +26,8 @@ import Icon, {
|
||||
UserOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Layout, Menu, Switch, Tooltip } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Layout, Menu } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BsKanban } from "react-icons/bs";
|
||||
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
||||
@@ -43,7 +42,6 @@ import { selectRecentItems, selectSelectedHeader } from "../../redux/application
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { signOutStart } from "../../redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { checkBeta, handleBeta, setBeta } from "../../utils/handleBeta";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
|
||||
@@ -115,19 +113,18 @@ function Header({
|
||||
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
const [betaSwitch, setBetaSwitch] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const isBeta = checkBeta();
|
||||
setBetaSwitch(isBeta);
|
||||
}, []);
|
||||
|
||||
const betaSwitchChange = (checked) => {
|
||||
setBeta(checked);
|
||||
setBetaSwitch(checked);
|
||||
handleBeta();
|
||||
};
|
||||
// const deleteBetaCookie = () => {
|
||||
// const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
|
||||
// if (cookieExists) {
|
||||
// const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||
// document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// deleteBetaCookie();
|
||||
|
||||
const accountingChildren = [];
|
||||
|
||||
@@ -695,31 +692,6 @@ function Header({
|
||||
}
|
||||
];
|
||||
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
args: [],
|
||||
imex: () => {
|
||||
menuItems.push({
|
||||
key: "beta-switch",
|
||||
id: "header-beta-switch",
|
||||
style: { marginLeft: "auto" },
|
||||
label: (
|
||||
<Tooltip
|
||||
title={`A more modern ${InstanceRenderManager({
|
||||
imex: t("titles.imexonline"),
|
||||
rome: t("titles.romeonline"),
|
||||
promanager: t("titles.promanager")
|
||||
})} is ready for you to try! You can switch back at any time.`}
|
||||
>
|
||||
<InfoCircleOutlined />
|
||||
<span style={{ marginRight: 8 }}>Try the new app</span>
|
||||
<Switch checked={betaSwitch} onChange={betaSwitchChange} />
|
||||
</Tooltip>
|
||||
)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Layout.Header>
|
||||
<Menu
|
||||
|
||||
@@ -23,6 +23,7 @@ import ScheduleEventColor from "./schedule-event.color.component";
|
||||
import ScheduleEventNote from "./schedule-event.note.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -127,6 +128,9 @@ export function ScheduleEventComponent({
|
||||
{(event.job && event.job.alt_transport) || ""}
|
||||
<ScheduleAtChange job={event && event.job} />
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.comment")} valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
<ProductionListColumnComment record={event && event.job} />
|
||||
</DataLabel>
|
||||
<ScheduleEventNote event={event} />
|
||||
</div>
|
||||
) : (
|
||||
@@ -316,6 +320,7 @@ export function ScheduleEventComponent({
|
||||
})`}
|
||||
|
||||
{event.job && event.job.alt_transport && <div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>}
|
||||
{event?.job?.comment && `C: ${event.job.comment}`}
|
||||
</Space>
|
||||
) : (
|
||||
<div
|
||||
|
||||
@@ -118,8 +118,7 @@ export function JobLinesComponent({
|
||||
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {})
|
||||
}
|
||||
}),
|
||||
sortOrder: state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
|
||||
ellipsis: true
|
||||
sortOrder: state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.oem_partno"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import axios from "axios";
|
||||
import { Badge, Card, Space, Table, Tag } from "antd";
|
||||
import { gql, useQuery } from "@apollo/client";
|
||||
@@ -72,7 +72,7 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
render: (text) => DateTimeFormatterFunction(text),
|
||||
sorter: (a, b) => day(a.start).unix() - day(b.start).unix()
|
||||
sorter: (a, b) => dayjs(a.start).unix() - dayjs(b.start).unix()
|
||||
},
|
||||
{
|
||||
title: t("job_lifecycle.columns.relative_start"),
|
||||
@@ -90,7 +90,7 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
}
|
||||
return isEmpty(a.end) ? 1 : -1;
|
||||
}
|
||||
return day(a.end).unix() - day(b.end).unix();
|
||||
return dayjs(a.end).unix() - dayjs(b.end).unix();
|
||||
},
|
||||
render: (text) => (isEmpty(text) ? t("job_lifecycle.content.not_available") : DateTimeFormatterFunction(text))
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Form, notification, Popover, Select, Space } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -48,7 +48,7 @@ export function JobLineDispatchButton({
|
||||
const result = await dispatchLines({
|
||||
variables: {
|
||||
partsDispatch: {
|
||||
dispatched_at: day(),
|
||||
dispatched_at: dayjs(),
|
||||
employeeid: values.employeeid,
|
||||
jobid: job.id,
|
||||
dispatched_by: currentUser.email,
|
||||
@@ -138,7 +138,11 @@ export function JobLineDispatchButton({
|
||||
|
||||
return (
|
||||
<Popover open={visible} content={popMenu}>
|
||||
<Button disabled={selectedLines.length === 0 || jobRO || disabled} loading={loading} onClick={() => setVisible(true)}>
|
||||
<Button
|
||||
disabled={selectedLines.length === 0 || jobRO || disabled}
|
||||
loading={loading}
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
{t("joblines.actions.dispatchparts", { count: selectedLines.length })}
|
||||
</Button>
|
||||
</Popover>
|
||||
|
||||
@@ -45,7 +45,8 @@ export default function JobLineNotePopup({ jobline, disabled }) {
|
||||
if (editing)
|
||||
return (
|
||||
<div>
|
||||
<Input
|
||||
<Input.TextArea
|
||||
autoSize
|
||||
autoFocus
|
||||
suffix={loading ? <LoadingSpinner /> : null}
|
||||
value={note}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Form, Input, InputNumber, Modal, Select, Switch } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InputCurrency from "../form-items-formatted/currency-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import JoblinesPreset from "../job-lines-preset-button/job-lines-preset-button.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -61,7 +61,7 @@ export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCa
|
||||
]}
|
||||
name="line_desc"
|
||||
>
|
||||
<Input />
|
||||
<Input.TextArea autoSize />
|
||||
</Form.Item>
|
||||
<JoblinesPreset form={form} />
|
||||
</LayoutFormRow>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { notification } from "antd";
|
||||
import Axios from "axios";
|
||||
import Dinero from "dinero.js";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -7,13 +10,10 @@ import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_NEW_JOB_LINE, UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
||||
import Axios from "axios";
|
||||
import Dinero from "dinero.js";
|
||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobLineEditModal: selectJobLineEditModal,
|
||||
@@ -82,13 +82,15 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
|
||||
variables: {
|
||||
lineId: jobLineEditModal.context.id,
|
||||
line: {
|
||||
...values,
|
||||
prt_dsmk_m: Dinero({
|
||||
amount: Math.round(values.act_price * 100)
|
||||
...UndefinedToNull({
|
||||
...values,
|
||||
prt_dsmk_m: Dinero({
|
||||
amount: Math.round(values.act_price * 100)
|
||||
})
|
||||
.percentage(Math.abs(values.prt_dsmk_p || 0))
|
||||
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
|
||||
.toFormat(0.0)
|
||||
})
|
||||
.percentage(Math.abs(values.prt_dsmk_p || 0))
|
||||
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
|
||||
.toFormat(0.0)
|
||||
}
|
||||
},
|
||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"]
|
||||
|
||||
@@ -141,10 +141,16 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||
key: t("jobs.fields.ded_amt"),
|
||||
total: job.job_totals.totals.custPayable.deductible
|
||||
},
|
||||
// {
|
||||
// key: t("jobs.fields.federal_tax_payable"),
|
||||
// total: job.job_totals.totals.custPayable.federal_tax,
|
||||
// },
|
||||
...InstanceRenderManager({
|
||||
imex: [
|
||||
{
|
||||
key: t("jobs.fields.federal_tax_payable"),
|
||||
total: job.job_totals.totals.custPayable.federal_tax
|
||||
}
|
||||
],
|
||||
rome: [],
|
||||
promanager: "USE_ROME"
|
||||
}),
|
||||
{
|
||||
key: t("jobs.fields.other_amount_payable"),
|
||||
total: job.job_totals.totals.custPayable.other_customer_amount
|
||||
@@ -154,11 +160,32 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||
total: job.job_totals.totals.custPayable.dep_taxes
|
||||
},
|
||||
|
||||
{
|
||||
key: t("jobs.labels.total_cust_payable"),
|
||||
total: job.job_totals.totals.custPayable.total,
|
||||
bold: true
|
||||
},
|
||||
...(bodyshop.intellipay_config?.enable_cash_discount
|
||||
? [
|
||||
{
|
||||
key: t("jobs.labels.total_cust_payable_cash_discount"),
|
||||
total: job.job_totals.totals.custPayable.total,
|
||||
bold: true
|
||||
},
|
||||
{
|
||||
key: t("jobs.labels.total_cust_payable"),
|
||||
total: Dinero(job.job_totals.totals.custPayable.total)
|
||||
.add(
|
||||
Dinero(job.job_totals.totals.custPayable.total).percentage(
|
||||
bodyshop.intellipay_config?.cash_discount_percentage || 0
|
||||
)
|
||||
)
|
||||
.toJSON(),
|
||||
bold: true
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
key: t("jobs.labels.total_cust_payable"),
|
||||
total: job.job_totals.totals.custPayable.total,
|
||||
bold: true
|
||||
}
|
||||
]),
|
||||
{
|
||||
key: t("jobs.labels.net_repairs"),
|
||||
total: job.job_totals.totals.net_repairs,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||
@@ -12,7 +13,6 @@ import Car from "../job-damage-visual/job-damage-visual.component";
|
||||
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
||||
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
|
||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -185,6 +185,9 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
<Form.Item label={t("jobs.fields.towin")} name="towin" valuePropName="checked">
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.tlos_ind")} name="tlos_ind" valuePropName="checked">
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
</FormRow>
|
||||
</Col>
|
||||
<Col {...lossColDamage}>
|
||||
|
||||
@@ -219,7 +219,7 @@ export function JobsExportAllButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -250,8 +250,8 @@ export function JobsList({ bodyshop }) {
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.estimator"),
|
||||
dataIndex: "jobs.labels.estimator",
|
||||
key: "jobs.labels.estimator",
|
||||
dataIndex: "estimator",
|
||||
key: "estimator",
|
||||
ellipsis: true,
|
||||
responsive: ["xl"],
|
||||
sorter: (a, b) =>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Col, notification, Row, Table } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_PARTS_DISPATCH_LINE } from "../../graphql/parts-dispatch.queries";
|
||||
@@ -11,7 +11,7 @@ export default function PartsDispatchExpander({ dispatch, job }) {
|
||||
const [updateDispatchLine] = useMutation(UPDATE_PARTS_DISPATCH_LINE);
|
||||
|
||||
const handleAccept = async ({ partsDispatchLineId }) => {
|
||||
const accepted_at = day();
|
||||
const accepted_at = dayjs();
|
||||
const result = await updateDispatchLine({
|
||||
variables: { id: partsDispatchLineId, line: { accepted_at } },
|
||||
optimisticResponse: {
|
||||
|
||||
@@ -242,7 +242,8 @@ export function PartsOrderListTableComponent({
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: (text, record) => recordActions(record, true)
|
||||
render: (text, record) => recordActions(record, true),
|
||||
id: "parts-order-list-table-actions"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ export default function PartsOrderModalPriceChange({ form, field }) {
|
||||
key: "25",
|
||||
label: t("parts_orders.labels.discount", { percent: "25%" })
|
||||
},
|
||||
{
|
||||
key: "40",
|
||||
label: t("parts_orders.labels.discount", { percent: "40%" })
|
||||
},
|
||||
{
|
||||
key: "custom",
|
||||
label: (
|
||||
|
||||
@@ -200,7 +200,7 @@ export function PayableExportAll({
|
||||
);
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -180,7 +180,7 @@ export function PaymentsExportAllButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -8,11 +8,12 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
@@ -20,7 +21,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PaymentsGenerateLink);
|
||||
|
||||
export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone, setMessage }) {
|
||||
export function PaymentsGenerateLink({ bodyshop, currentUser, callback, job, openChatByPhone, setMessage }) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@@ -30,29 +31,35 @@ export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone,
|
||||
|
||||
const handleFinish = async ({ amount }) => {
|
||||
setLoading(true);
|
||||
|
||||
const p = parsePhoneNumber(job.ownr_ph1, "CA");
|
||||
let p;
|
||||
try {
|
||||
p = parsePhoneNumber(job.ownr_ph1 || "", "CA");
|
||||
} catch (error) {
|
||||
console.log("Unable to parse phone number");
|
||||
}
|
||||
setLoading(true);
|
||||
const response = await axios.post("/intellipay/generate_payment_url", {
|
||||
bodyshop,
|
||||
amount: amount,
|
||||
account: job.ro_number,
|
||||
invoice: job.id
|
||||
comment: btoa(JSON.stringify({ payments: [{ jobid: job.id, amount }], userEmail: currentUser.email }))
|
||||
});
|
||||
setLoading(false);
|
||||
setPaymentLink(response.data.shorUrl);
|
||||
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: job.id
|
||||
});
|
||||
setMessage(
|
||||
t("payments.labels.smspaymentreminder", {
|
||||
shopname: bodyshop.shopname,
|
||||
amount: amount,
|
||||
payment_link: response.data.shorUrl
|
||||
})
|
||||
);
|
||||
if (p) {
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: job.id
|
||||
});
|
||||
setMessage(
|
||||
t("payments.labels.smspaymentreminder", {
|
||||
shopname: bodyshop.shopname,
|
||||
amount: amount,
|
||||
payment_link: response.data.shorUrl
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
//Add in confirmation & errors.
|
||||
if (callback) callback();
|
||||
|
||||
@@ -6,11 +6,11 @@ import {
|
||||
PauseCircleOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Col, Row, Space, Tooltip } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
|
||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||
@@ -18,8 +18,8 @@ import ProductionSubletsManageComponent from "../production-sublets-manage/produ
|
||||
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
const cardColor = (ssbuckets, totalHrs) => {
|
||||
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
|
||||
@@ -213,21 +213,13 @@ const EstimatorToolTip = ({ metadata, cardSettings }) => {
|
||||
};
|
||||
|
||||
const SubtotalTooltip = ({ metadata, cardSettings, t }) => {
|
||||
const amount = metadata?.job_totals?.totals?.subtotal?.amount;
|
||||
const dineroAmount = amount ? Dinero({ amount: parseInt(amount * 100) }).toFormat("0,0.00") : null;
|
||||
const dineroAmount = Dinero(metadata?.job_totals?.totals?.subtotal ?? Dinero()).toFormat();
|
||||
|
||||
return (
|
||||
cardSettings?.subtotal && (
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip
|
||||
title={!!amount ? `${t("production.statistics.currency_symbol")}${dineroAmount}` : null}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{!!amount ? (
|
||||
<span>{`${t("production.statistics.currency_symbol")}${dineroAmount}`}</span>
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
<EllipsesToolTip title={`${dineroAmount}`} kiosk={cardSettings.kiosk}>
|
||||
{dineroAmount}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
)
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import Board from "./trello-board/index";
|
||||
import { Button, notification, Skeleton, Space } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, notification, Skeleton, Space } from "antd";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { generate_UPDATE_JOB_KANBAN } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
@@ -15,14 +17,13 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
|
||||
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
|
||||
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
||||
import ProductionListPrint from "../production-list-table/production-list-print.component.jsx";
|
||||
import CardColorLegend from "./production-board-kanban-card-color-legend.component.jsx";
|
||||
import "./production-board-kanban.styles.scss";
|
||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { defaultFilters, mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
|
||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
|
||||
import Board from "./trello-board/index";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -185,7 +186,7 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
|
||||
const cardSettings = useMemo(() => {
|
||||
const kanbanSettings = associationSettings?.kanban_settings;
|
||||
return mergeWithDefaults(kanbanSettings);
|
||||
}, [associationSettings]);
|
||||
}, [associationSettings?.kanban_settings]);
|
||||
|
||||
const handleSettingsChange = () => {
|
||||
setFilter(defaultFilters);
|
||||
@@ -214,6 +215,7 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
|
||||
bodyshop={bodyshop}
|
||||
data={data}
|
||||
/>
|
||||
<ProductionListPrint />
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,18 +1,40 @@
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import { useQuery, useSubscription } from "@apollo/client";
|
||||
import React, { useContext, useEffect, useMemo, useRef } from "react";
|
||||
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_JOBS_IN_PRODUCTION, SUBSCRIPTION_JOBS_IN_PRODUCTION } from "../../graphql/jobs.queries";
|
||||
import {
|
||||
QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||
QUERY_JOBS_IN_PRODUCTION,
|
||||
SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||
SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW
|
||||
} from "../../graphql/jobs.queries";
|
||||
import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
||||
function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionType = "direct" }) {
|
||||
const fired = useRef(false);
|
||||
const client = useApolloClient();
|
||||
const { socket } = useContext(SocketContext); // Get the socket from context
|
||||
const reconnectTimeout = useRef(null); // To store the reconnect timeout
|
||||
const disconnectTime = useRef(null); // To track disconnection time
|
||||
const acceptableReconnectTime = 2000; // 2 seconds threshold
|
||||
|
||||
const {
|
||||
treatments: { Websocket_Production }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Websocket_Production"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const combinedStatuses = useMemo(
|
||||
() => [
|
||||
...bodyshop.md_ro_statuses.production_statuses,
|
||||
@@ -28,26 +50,127 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
||||
onError: (error) => console.error(`Error fetching jobs in production: ${error.message}`)
|
||||
});
|
||||
|
||||
const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION, {
|
||||
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
|
||||
});
|
||||
const subscriptionEnabled = Websocket_Production?.treatment === "off";
|
||||
|
||||
const { data: updatedJobs } = useSubscription(
|
||||
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||
{
|
||||
skip: !subscriptionEnabled,
|
||||
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
|
||||
}
|
||||
);
|
||||
|
||||
const { loading: associationSettingsLoading, data: associationSettings } = useQuery(QUERY_KANBAN_SETTINGS, {
|
||||
variables: { email: currentUser.email },
|
||||
onError: (error) => console.error(`Error fetching Kanban settings: ${error.message}`)
|
||||
});
|
||||
|
||||
// const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
|
||||
|
||||
useEffect(() => {
|
||||
if (updatedJobs && data) {
|
||||
if (subscriptionEnabled) {
|
||||
if (!updatedJobs) {
|
||||
return;
|
||||
}
|
||||
if (!fired.current) {
|
||||
fired.current = true;
|
||||
return;
|
||||
}
|
||||
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
||||
}
|
||||
}, [updatedJobs, data, refetch]);
|
||||
}, [updatedJobs, refetch, subscriptionEnabled]);
|
||||
|
||||
// Socket.IO implementation for users with Split treatment "off"
|
||||
useEffect(() => {
|
||||
if (subscriptionEnabled || !socket || !bodyshop || !bodyshop.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handleJobUpdates = async (jobChangedData) => {
|
||||
const jobId = jobChangedData.id;
|
||||
|
||||
// Access the existing cache for QUERY_JOBS_IN_PRODUCTION
|
||||
const existingJobsCache = client.readQuery({
|
||||
query: QUERY_JOBS_IN_PRODUCTION
|
||||
});
|
||||
|
||||
const existingJobs = existingJobsCache?.jobs || [];
|
||||
|
||||
// Check if the job already exists in the cached jobs
|
||||
const existingJob = existingJobs.find((job) => job.id === jobId);
|
||||
|
||||
if (existingJob) {
|
||||
// If the job exists, we update the cache without making any additional queries
|
||||
client.writeQuery({
|
||||
query: QUERY_JOBS_IN_PRODUCTION,
|
||||
data: {
|
||||
jobs: existingJobs.map((job) =>
|
||||
job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "jobs" } : job
|
||||
)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If the job doesn't exist, fetch it from the server and then add it to the cache
|
||||
try {
|
||||
const { data: jobData } = await client.query({
|
||||
query: QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||
variables: { id: jobId },
|
||||
fetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
// Add the job to the existing cached jobs
|
||||
client.writeQuery({
|
||||
query: QUERY_JOBS_IN_PRODUCTION,
|
||||
data: {
|
||||
jobs: [...existingJobs, { ...jobData.job, __typename: "jobs" }]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error fetching job ${jobId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
// Capture the disconnection time
|
||||
disconnectTime.current = Date.now();
|
||||
};
|
||||
|
||||
const handleReconnect = () => {
|
||||
const reconnectTime = Date.now();
|
||||
const disconnectionDuration = reconnectTime - disconnectTime.current;
|
||||
|
||||
// Only refetch if disconnection was longer than the acceptable reconnect time
|
||||
if (disconnectionDuration >= acceptableReconnectTime) {
|
||||
if (!reconnectTimeout.current) {
|
||||
reconnectTimeout.current = setTimeout(() => {
|
||||
const randomDelay = Math.floor(Math.random() * (30000 - 10000 + 1)) + 10000; // Random delay between 10 and 30 seconds
|
||||
setTimeout(() => {
|
||||
if (refetch) refetch().catch((err) => console.error(`Issue `));
|
||||
reconnectTimeout.current = null; // Clear the timeout reference after refetch
|
||||
}, randomDelay);
|
||||
}, acceptableReconnectTime);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Listen for 'job-changed', 'disconnect', and 'connect' events
|
||||
socket.on("production-job-updated", handleJobUpdates);
|
||||
socket.on("disconnect", handleDisconnect);
|
||||
socket.on("connect", handleReconnect);
|
||||
|
||||
// Clean up on unmount or when dependencies change
|
||||
return () => {
|
||||
socket.off("production-job-updated", handleJobUpdates);
|
||||
socket.off("disconnect", handleDisconnect);
|
||||
socket.off("connect", handleReconnect);
|
||||
if (reconnectTimeout.current) {
|
||||
clearTimeout(reconnectTimeout.current);
|
||||
}
|
||||
};
|
||||
}, [subscriptionEnabled, socket, bodyshop, client, refetch]);
|
||||
|
||||
const filteredAssociationSettings = useMemo(() => {
|
||||
return associationSettings?.associations[0] || null;
|
||||
}, [associationSettings]);
|
||||
}, [associationSettings?.associations]);
|
||||
|
||||
return (
|
||||
<ProductionBoardKanbanComponent
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, Statistic } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
export const StatisticType = {
|
||||
HOURS: "hours",
|
||||
@@ -32,7 +33,21 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
};
|
||||
|
||||
const calculateTotalAmount = (items, key) => {
|
||||
return items.reduce((acc, item) => acc + (item[key]?.totals?.subtotal?.amount || 0), 0);
|
||||
return items.reduce((acc, item) => acc.add(Dinero(item[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 }));
|
||||
};
|
||||
|
||||
const calculateReducerTotalAmount = (lanes, key) => {
|
||||
return lanes.reduce(
|
||||
(acc, lane) => {
|
||||
return acc.add(
|
||||
lane.cards.reduce(
|
||||
(laneAcc, card) => laneAcc.add(Dinero(card.metadata[key]?.totals?.subtotal ?? Dinero())),
|
||||
Dinero({ amount: 0 })
|
||||
)
|
||||
);
|
||||
},
|
||||
Dinero({ amount: 0 })
|
||||
);
|
||||
};
|
||||
|
||||
const calculateReducerTotal = (lanes, key, subKey) => {
|
||||
@@ -43,14 +58,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const calculateReducerTotalAmount = (lanes, key) => {
|
||||
return lanes.reduce((acc, lane) => {
|
||||
return (
|
||||
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata[key]?.totals?.subtotal?.amount || 0), 0)
|
||||
);
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const formatValue = (value, type) => {
|
||||
if (type === StatisticType.JOBS) {
|
||||
return value.toFixed(0);
|
||||
@@ -87,9 +94,15 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
const totalAmountInProduction = useMemo(() => {
|
||||
if (!cardSettings.totalAmountInProduction) return null;
|
||||
const total = calculateTotalAmount(data, "job_totals");
|
||||
return parseFloat(total.toFixed(2));
|
||||
return total.toFormat("$0,0.00");
|
||||
}, [data, cardSettings.totalAmountInProduction]);
|
||||
|
||||
const totalAmountOnBoard = useMemo(() => {
|
||||
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
|
||||
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
|
||||
return total.toFormat("$0,0.00");
|
||||
}, [reducerData, cardSettings.totalAmountOnBoard]);
|
||||
|
||||
const totalHrsOnBoard = useMemo(() => {
|
||||
if (!reducerData || !cardSettings.totalHrsOnBoard) return null;
|
||||
const total =
|
||||
@@ -118,12 +131,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
[reducerData, cardSettings.jobsOnBoard]
|
||||
);
|
||||
|
||||
const totalAmountOnBoard = useMemo(() => {
|
||||
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
|
||||
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
|
||||
return parseFloat(total.toFixed(2));
|
||||
}, [reducerData, cardSettings.totalAmountOnBoard]);
|
||||
|
||||
const tasksInProduction = useMemo(() => {
|
||||
if (!data || !cardSettings.tasksInProduction) return null;
|
||||
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
||||
@@ -191,7 +198,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
<Statistic
|
||||
title={t(`production.statistics.${stat.label}`)}
|
||||
value={formatValue(stat.value, stat.type)}
|
||||
prefix={stat.type === StatisticType.AMOUNT ? t("production.statistics.currency_symbol") : undefined}
|
||||
suffix={
|
||||
stat.type === StatisticType.HOURS
|
||||
? t("production.statistics.hours")
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
|
||||
.production-alert {
|
||||
background: transparent;
|
||||
border: none;
|
||||
@@ -70,3 +69,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.clone.is-dragging .ant-card {
|
||||
border: #1890ff 2px solid !important;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,66 @@
|
||||
import InstanceRenderManager from "../../../utils/instanceRenderMgr.js";
|
||||
|
||||
const statisticsItems = [
|
||||
{ id: 0, name: "totalHrs", label: "total_hours_in_production" },
|
||||
{ id: 1, name: "totalAmountInProduction", label: "total_amount_in_production" },
|
||||
{ id: 2, name: "totalLAB", label: "total_lab_in_production" },
|
||||
{ id: 3, name: "totalLAR", label: "total_lar_in_production" },
|
||||
{ id: 4, name: "jobsInProduction", label: "jobs_in_production" },
|
||||
{ id: 5, name: "totalHrsOnBoard", label: "total_hours_on_board" },
|
||||
{ id: 6, name: "totalAmountOnBoard", label: "total_amount_on_board" },
|
||||
{ id: 7, name: "totalLABOnBoard", label: "total_lab_on_board" },
|
||||
{ id: 8, name: "totalLAROnBoard", label: "total_lar_on_board" },
|
||||
{ id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" },
|
||||
{ id: 10, name: "tasksOnBoard", label: "tasks_on_board" },
|
||||
|
||||
{
|
||||
id: 5,
|
||||
name: "totalHrsOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_hours_in_view",
|
||||
rome: "total_hours_on_board",
|
||||
promanager: "total_hours_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "totalAmountOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_amount_in_view",
|
||||
rome: "total_amount_on_board",
|
||||
promanager: "total_amount_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "totalLABOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_lab_in_view",
|
||||
rome: "total_lab_on_board",
|
||||
promanager: "total_lab_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "totalLAROnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_lar_in_view",
|
||||
rome: "total_lar_on_board",
|
||||
promanager: "total_lar_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "jobsOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "total_jobs_in_view",
|
||||
rome: "total_jobs_on_board",
|
||||
promanager: "total_jobs_on_board"
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "tasksOnBoard",
|
||||
label: InstanceRenderManager({
|
||||
imex: "tasks_in_view",
|
||||
rome: "tasks_on_board",
|
||||
promanager: "tasks_on_board"
|
||||
})
|
||||
},
|
||||
{ id: 11, name: "tasksInProduction", label: "tasks_in_production" }
|
||||
];
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ function getFurthestAway({ pageBorderBox, draggable, candidates }) {
|
||||
const axis = candidate.axis;
|
||||
const target = patch(
|
||||
candidate.axis.line,
|
||||
// use the current center of the dragging item on the main axis
|
||||
pageBorderBox.center[axis.line],
|
||||
// use the center of the list on the main axis
|
||||
candidate.page.borderBox.center[axis.line],
|
||||
// use the center of the list on the cross axis
|
||||
candidate.page.borderBox.center[axis.crossAxisLine]
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import getBodyElement from "../get-body-element";
|
||||
const isEqual = (base) => (value) => base === value;
|
||||
const isScroll = isEqual("scroll");
|
||||
const isAuto = isEqual("auto");
|
||||
const isOverlay = isEqual("overlay");
|
||||
const isVisible = isEqual("visible");
|
||||
const isEither = (overflow, fn) => fn(overflow.overflowX) || fn(overflow.overflowY);
|
||||
const isBoth = (overflow, fn) => fn(overflow.overflowX) && fn(overflow.overflowY);
|
||||
@@ -14,7 +15,7 @@ const isElementScrollable = (el) => {
|
||||
overflowX: style.overflowX,
|
||||
overflowY: style.overflowY
|
||||
};
|
||||
return isEither(overflow, isScroll) || isEither(overflow, isAuto);
|
||||
return isEither(overflow, isScroll) || isEither(overflow, isAuto) || isEither(overflow, isOverlay);
|
||||
};
|
||||
|
||||
// Special case for a body element
|
||||
|
||||
@@ -8,7 +8,7 @@ function getSelector(contextId) {
|
||||
return `[${attributes.dragHandle.contextId}="${contextId}"]`;
|
||||
}
|
||||
|
||||
function findClosestDragHandleFromEvent(contextId, event) {
|
||||
export function findClosestDragHandleFromEvent(contextId, event) {
|
||||
const target = event.target;
|
||||
if (!isElement(target)) {
|
||||
warning("event.target must be a Element");
|
||||
|
||||
@@ -240,11 +240,14 @@ export default function useTouchSensor(api) {
|
||||
y: clientY
|
||||
};
|
||||
|
||||
const handle = api.findClosestDragHandle(event);
|
||||
invariant(handle, "Touch sensor unable to find drag handle");
|
||||
|
||||
// unbind this event handler
|
||||
unbindEventsRef.current();
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
startPendingDrag(actions, point);
|
||||
startPendingDrag(actions, point, handle);
|
||||
}
|
||||
}),
|
||||
// not including stop or startPendingDrag as it is not defined initially
|
||||
@@ -288,7 +291,7 @@ export default function useTouchSensor(api) {
|
||||
}
|
||||
}, [stop]);
|
||||
const bindCapturingEvents = useCallback(
|
||||
function bindCapturingEvents() {
|
||||
function bindCapturingEvents(target) {
|
||||
const options = {
|
||||
capture: true,
|
||||
passive: false
|
||||
@@ -307,7 +310,7 @@ export default function useTouchSensor(api) {
|
||||
// Old behaviour:
|
||||
// https://gist.github.com/parris/dda613e3ae78f14eb2dc9fa0f4bfce3d
|
||||
// https://stackoverflow.com/questions/33298828/touch-move-event-dont-fire-after-touch-start-target-is-removed
|
||||
const unbindTarget = bindEvents(window, getHandleBindings(args), options);
|
||||
const unbindTarget = bindEvents(target, getHandleBindings(args), options);
|
||||
const unbindWindow = bindEvents(window, getWindowBindings(args), options);
|
||||
unbindEventsRef.current = function unbindAll() {
|
||||
unbindTarget();
|
||||
@@ -330,7 +333,7 @@ export default function useTouchSensor(api) {
|
||||
[getPhase, setPhase]
|
||||
);
|
||||
const startPendingDrag = useCallback(
|
||||
function startPendingDrag(actions, point) {
|
||||
function startPendingDrag(actions, point, target) {
|
||||
invariant(getPhase().type === "IDLE", "Expected to move from IDLE to PENDING drag");
|
||||
const longPressTimerId = setTimeout(startDragging, timeForLongPress);
|
||||
setPhase({
|
||||
@@ -339,7 +342,7 @@ export default function useTouchSensor(api) {
|
||||
actions,
|
||||
longPressTimerId
|
||||
});
|
||||
bindCapturingEvents();
|
||||
bindCapturingEvents(target);
|
||||
},
|
||||
[bindCapturingEvents, getPhase, setPhase, startDragging]
|
||||
);
|
||||
|
||||
@@ -23,7 +23,9 @@ import getBorderBoxCenterPosition from "../get-border-box-center-position";
|
||||
import { warning } from "../../dev-warning";
|
||||
import useLayoutEffect from "../use-isomorphic-layout-effect";
|
||||
import { noop } from "../../empty";
|
||||
import findClosestDraggableIdFromEvent from "./find-closest-draggable-id-from-event";
|
||||
import findClosestDraggableIdFromEvent, {
|
||||
findClosestDragHandleFromEvent
|
||||
} from "./find-closest-draggable-id-from-event";
|
||||
import findDraggable from "../get-elements/find-draggable";
|
||||
import bindEvents from "../event-bindings/bind-events";
|
||||
|
||||
@@ -339,6 +341,9 @@ export default function useSensorMarshal({ contextId, store, registry, customSen
|
||||
}),
|
||||
[contextId, lockAPI, registry, store]
|
||||
);
|
||||
|
||||
const findClosestDragHandle = useCallback((event) => findClosestDragHandleFromEvent(contextId, event), [contextId]);
|
||||
|
||||
const findClosestDraggableId = useCallback((event) => findClosestDraggableIdFromEvent(contextId, event), [contextId]);
|
||||
const findOptionsForDraggable = useCallback(
|
||||
(id) => {
|
||||
@@ -370,9 +375,18 @@ export default function useSensorMarshal({ contextId, store, registry, customSen
|
||||
findClosestDraggableId,
|
||||
findOptionsForDraggable,
|
||||
tryReleaseLock,
|
||||
isLockClaimed
|
||||
isLockClaimed,
|
||||
findClosestDragHandle
|
||||
}),
|
||||
[canGetLock, tryGetLock, findClosestDraggableId, findOptionsForDraggable, tryReleaseLock, isLockClaimed]
|
||||
[
|
||||
canGetLock,
|
||||
tryGetLock,
|
||||
findClosestDraggableId,
|
||||
findOptionsForDraggable,
|
||||
tryReleaseLock,
|
||||
isLockClaimed,
|
||||
findClosestDragHandle
|
||||
]
|
||||
);
|
||||
|
||||
// Bad ass
|
||||
|
||||
@@ -83,7 +83,13 @@ const getFinalStyles = (contextId) => {
|
||||
return {
|
||||
selector: getSelector(attributes.draggable.contextId),
|
||||
styles: {
|
||||
dragging: transition,
|
||||
dragging: `
|
||||
${transition}
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
`,
|
||||
dropAnimating: transition,
|
||||
userCancel: transition
|
||||
}
|
||||
|
||||
@@ -67,7 +67,9 @@ export default function useStyleMarshal(contextId, nonce) {
|
||||
const remove = (ref) => {
|
||||
const current = ref.current;
|
||||
invariant(current, "Cannot unmount ref as it is not set");
|
||||
getHead().removeChild(current);
|
||||
if (getHead().contains(current)) {
|
||||
getHead().removeChild(current);
|
||||
}
|
||||
ref.current = null;
|
||||
};
|
||||
remove(alwaysRef);
|
||||
|
||||
@@ -298,6 +298,16 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => statusSort(a.status, b.status, activeStatuses),
|
||||
sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
filters:
|
||||
activeStatuses
|
||||
?.map((s) => {
|
||||
return {
|
||||
text: s || "No Status*",
|
||||
value: [s]
|
||||
};
|
||||
})
|
||||
.sort((a, b) => statusSort(a.text, b.text, activeStatuses)) || [],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
render: (text, record) => <ProductionListColumnStatus record={record} />
|
||||
},
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user