Compare commits
885 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b479684fe4 | ||
|
|
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 | ||
|
|
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 | ||
|
|
2a2d399a98 | ||
|
|
5f513a8bef | ||
|
|
4b96d5a707 | ||
|
|
220f3d4410 | ||
|
|
841f62bd84 | ||
|
|
f3f16b78d5 | ||
|
|
91e2e7931b | ||
|
|
1e855799f8 | ||
|
|
3c6faf8473 | ||
|
|
c994eaaa8e | ||
|
|
517d8f4163 | ||
|
|
9deb2964a5 | ||
|
|
9cf9f8b844 | ||
|
|
ad46ea74c0 | ||
|
|
2a28855e4b | ||
|
|
8d25f60097 | ||
|
|
982a51f16e | ||
|
|
68d02648d7 | ||
|
|
6e8122849a | ||
|
|
b04ae84941 | ||
|
|
932979d5fb | ||
|
|
f7ef32c58d | ||
|
|
f7108b4b8c | ||
|
|
882038a794 | ||
|
|
aec23fe46b | ||
|
|
89d5b1cfe4 | ||
|
|
35ac0b0c6a | ||
|
|
2a2a0f8961 | ||
|
|
d9902b9744 | ||
|
|
f82478a362 | ||
|
|
bb3d3fbe72 | ||
|
|
4fa0593bb5 | ||
|
|
41517ca7d4 | ||
|
|
35c9f649ad | ||
|
|
ad2f2e55a5 | ||
|
|
41c446ddb3 | ||
|
|
7d6aa8489d | ||
|
|
63f1e0f07c | ||
|
|
98f4423624 | ||
|
|
1ac4cbb59f | ||
|
|
24ebfbfbf5 | ||
|
|
7ff1051d3c | ||
|
|
8af3364660 | ||
|
|
02f4677aef | ||
|
|
11785f3b86 | ||
|
|
90532427b6 | ||
|
|
cc9979ff4b | ||
|
|
c89e4f1b41 | ||
|
|
c3e6d3dc48 | ||
|
|
ad1ce7b220 | ||
|
|
fd4dbdfb3a | ||
|
|
153cf6a840 | ||
|
|
a567d0d6dd | ||
|
|
297599a45b | ||
|
|
678ca591c1 | ||
|
|
c19f8167e8 | ||
|
|
cc2d474fda | ||
|
|
9058aca16e | ||
|
|
1c186f7fa5 | ||
|
|
46da3285f8 | ||
|
|
b419929ad7 | ||
|
|
8018daa2dc | ||
|
|
1e7c285fef | ||
|
|
0b072e6089 | ||
|
|
4fd6203987 | ||
|
|
51d264098c | ||
|
|
680a66b156 | ||
|
|
481a14e529 | ||
|
|
f3e43334c4 | ||
|
|
0054b00d01 | ||
|
|
82ecb5533f | ||
|
|
d3289d85f1 | ||
|
|
e628b1364c | ||
|
|
6c421c1447 | ||
|
|
99369e7040 | ||
|
|
01cbdf14a9 | ||
|
|
f691aca241 | ||
|
|
85495a11e3 | ||
|
|
134ce05d27 | ||
|
|
3498fbc8f1 | ||
|
|
f49f72ce7f | ||
|
|
a5e3b6ce33 | ||
|
|
0fd945b859 | ||
|
|
879eba0247 | ||
|
|
bb49dd77a1 | ||
|
|
ae705322f8 | ||
|
|
36d92d4060 | ||
|
|
3ce2b1ab19 | ||
|
|
52e756a78a | ||
|
|
5a36cb7cf1 | ||
|
|
9138f4be16 | ||
|
|
df93357cec | ||
|
|
8ab23c4ca6 | ||
|
|
f179d69420 | ||
|
|
730a7a233d | ||
|
|
84ad10fa9c | ||
|
|
b1cda41f56 | ||
|
|
0bce921f69 | ||
|
|
97282740f5 | ||
|
|
150ae02978 | ||
|
|
70058b6bd4 | ||
|
|
67c63f81d9 | ||
|
|
abeffb2a19 | ||
|
|
fb8452b2bb | ||
|
|
1f281e8439 | ||
|
|
223c705e8f | ||
|
|
00d8b533f4 | ||
|
|
3b25a8fe07 | ||
|
|
a57b9cddb5 | ||
|
|
064ed1bb8b | ||
|
|
efda254981 | ||
|
|
3fa6b8b6ac | ||
|
|
4603643240 | ||
|
|
f82b02958f | ||
|
|
5d1f61753b | ||
|
|
883043cde3 | ||
|
|
85ce7c638d | ||
|
|
547e279693 | ||
|
|
b9bdefe898 | ||
|
|
c963cb6fcc | ||
|
|
fa6d4cce2a | ||
|
|
c7188d5a71 | ||
|
|
c333d72743 | ||
|
|
2a1ec4eff3 | ||
|
|
190b2e5b5c | ||
|
|
3e2a517272 | ||
|
|
a6cd35bd06 | ||
|
|
ba3032e553 | ||
|
|
20fd452335 | ||
|
|
8f83a5f5e6 | ||
|
|
4e417d1b10 | ||
|
|
3ee63a503a | ||
|
|
8dd0e12398 | ||
|
|
253b4d0ddc | ||
|
|
a8b0931659 | ||
|
|
11a182c68a | ||
|
|
2583d21cf1 | ||
|
|
aa6032d74f | ||
|
|
e1bac5fe9f | ||
|
|
0c74848c54 | ||
|
|
1c4f2d2de0 | ||
|
|
d67d2aa064 | ||
|
|
208f3281a9 | ||
|
|
fb7e3bc812 | ||
|
|
884adb963c | ||
|
|
29c4df9f76 | ||
|
|
f1940a320c | ||
|
|
a297bba193 | ||
|
|
375a3fe386 | ||
|
|
d837754ef6 | ||
|
|
49a1f0c42c | ||
|
|
a5033e10cd | ||
|
|
00f0952ab2 | ||
|
|
ec5b636c26 | ||
|
|
7126cc0d56 | ||
|
|
568d8887ce | ||
|
|
4927e84a88 | ||
|
|
d2d8039cb9 | ||
|
|
5da242d785 | ||
|
|
87f25b1ed4 | ||
|
|
4f9afc8578 | ||
|
|
defadf70e3 | ||
|
|
82be3853f0 | ||
|
|
ff184926fc | ||
|
|
0e9768c0f6 | ||
|
|
805c924a15 | ||
|
|
10ff8fc432 | ||
|
|
e2db0bab9f | ||
|
|
92ee5169ab | ||
|
|
5cf2345dca | ||
|
|
0accf6a18f | ||
|
|
52e46aad86 | ||
|
|
43f49e5e51 | ||
|
|
60908b123d | ||
|
|
5169fd178a | ||
|
|
bbc446ef01 | ||
|
|
65bb8c6f26 | ||
|
|
171f40819c | ||
|
|
4baa26cb25 | ||
|
|
01da7fde31 | ||
|
|
3c8234d715 | ||
|
|
866f242465 | ||
|
|
f690596825 | ||
|
|
5f494e4b78 | ||
|
|
7b814329da | ||
|
|
2fc0e1cf50 | ||
|
|
ba3f98aeb4 | ||
|
|
f49818ade3 | ||
|
|
b37c70494b | ||
|
|
ff1db26f41 | ||
|
|
3550c97966 | ||
|
|
956c686360 | ||
|
|
508b0d7711 | ||
|
|
abb1464e30 | ||
|
|
704d5415d6 | ||
|
|
6cc1cfd1b0 | ||
|
|
e6455a7fd3 | ||
|
|
a9aad2a751 | ||
|
|
1bf745345d | ||
|
|
63c71ed923 | ||
|
|
3a844aefa0 | ||
|
|
4a16df36dd | ||
|
|
93c92e8976 | ||
|
|
9523e5534d | ||
|
|
d20048026c | ||
|
|
7ba3ed2b89 | ||
|
|
2cc0b7d741 | ||
|
|
97693fbcff | ||
|
|
5e94b1a71e | ||
|
|
970275e62a | ||
|
|
15c7b6f1c8 | ||
|
|
aa076da255 | ||
|
|
3dd154de79 | ||
|
|
72a2366abe | ||
|
|
36f517e8e1 | ||
|
|
981fb57d36 | ||
|
|
3149c3cfc6 | ||
|
|
a059c2b5a8 | ||
|
|
f91e0ec39f | ||
|
|
c31d4096c6 | ||
|
|
118f14ed4c | ||
|
|
207bb39672 | ||
|
|
0d0791cc41 | ||
|
|
16e34e4ed9 | ||
|
|
cd04f2b2b2 | ||
|
|
26e164b4d1 | ||
|
|
f45aa69917 | ||
|
|
4b44ff29ee | ||
|
|
f15f5d7309 | ||
|
|
ae6b2fe4f5 | ||
|
|
4ca686126a | ||
|
|
ea55b1a797 | ||
|
|
668366e886 | ||
|
|
eb1b2aef55 | ||
|
|
906430add5 | ||
|
|
f8fbbd2323 | ||
|
|
adab6ef0c6 | ||
|
|
ddfd91617f | ||
|
|
8418b4cb91 | ||
|
|
e593943d99 | ||
|
|
b58b5e65dc | ||
|
|
6d393987e8 | ||
|
|
c254a8abfe | ||
|
|
852231b503 | ||
|
|
f864e40a90 | ||
|
|
802f70dde8 | ||
|
|
6cb4fd6b93 | ||
|
|
70ca6edcb2 | ||
|
|
9dcc861740 | ||
|
|
fecb7fb24b | ||
|
|
c5b94db8af | ||
|
|
6690c9c692 | ||
|
|
506edcb3c6 | ||
|
|
c45e53e38b | ||
|
|
d8d8a4701e | ||
|
|
970c4ee9e1 | ||
|
|
c2386f43ef | ||
|
|
870878d151 | ||
|
|
cdf7bcf839 | ||
|
|
0fd3e75862 | ||
|
|
fcd3234fd9 | ||
|
|
2afa810e6c | ||
|
|
ff33b924b2 | ||
|
|
8a4fee7aea | ||
|
|
fa578efee4 | ||
|
|
4b6b4c0c63 | ||
|
|
8199ab83ef | ||
|
|
8e50d0ba53 | ||
|
|
7d72d66a97 | ||
|
|
3454694887 | ||
|
|
2a51c8a2bf | ||
|
|
524f623fd4 | ||
|
|
7ab7e18d96 | ||
|
|
6f2c8dba5a | ||
|
|
162d8bfffe | ||
|
|
61569d97cb | ||
|
|
975622a31c | ||
|
|
7524d06126 | ||
|
|
7f75eeadb9 | ||
|
|
4db2b73397 | ||
|
|
84bb25985b | ||
|
|
2f493c63f8 | ||
|
|
2761356f02 | ||
|
|
e38a58550f | ||
|
|
25ef4c6228 | ||
|
|
8207a52b6b | ||
|
|
8bbe7a1f0f | ||
|
|
2b052b3d43 | ||
|
|
94e7bf3f92 | ||
|
|
277fbeebc8 | ||
|
|
64e3f5f2ac | ||
|
|
399df78957 | ||
|
|
294325343b | ||
|
|
ed17eec948 | ||
|
|
f87c95079c | ||
|
|
327149ffc9 | ||
|
|
f81b21b933 | ||
|
|
a559b56983 | ||
|
|
6a9030b653 | ||
|
|
bb7f7deb50 | ||
|
|
55c10890bd | ||
|
|
fc7da187f4 | ||
|
|
edef0b194d | ||
|
|
f593f83ec1 | ||
|
|
5752f123ac | ||
|
|
37f0c98e75 | ||
|
|
6396d68584 | ||
|
|
bfd29f25dd | ||
|
|
e75e35e4ee | ||
|
|
48f18514af | ||
|
|
d7ddbf7e8d | ||
|
|
1b0198af63 | ||
|
|
ace16ba873 | ||
|
|
76c13700be | ||
|
|
f57a4bd948 | ||
|
|
8ae8737fe2 | ||
|
|
fef4393f9c | ||
|
|
47adb6d40a | ||
|
|
4940b10910 | ||
|
|
9c8e241ef7 | ||
|
|
36f7b7a1a1 | ||
|
|
c3d6b98c89 | ||
|
|
4b49654083 | ||
|
|
78223078f4 | ||
|
|
ca4b78d44c | ||
|
|
b0a5f2d998 | ||
|
|
19c980bc3b | ||
|
|
78d6b9699b | ||
|
|
82d8cb22df | ||
|
|
d7bfc789e2 | ||
|
|
b54d0ed62a | ||
|
|
8386443cb0 | ||
|
|
9c41af82d7 | ||
|
|
2997dd4e4d | ||
|
|
4602648fe5 | ||
|
|
a8f2ca5643 | ||
|
|
7959dc67ce | ||
|
|
0d2cdec75c | ||
|
|
cd055f7344 | ||
|
|
a4116b6c28 | ||
|
|
269ef25ece | ||
|
|
c43709395f | ||
|
|
575fbd5357 | ||
|
|
10f60752c8 | ||
|
|
870f8463db | ||
|
|
2ad887fb82 | ||
|
|
40a1a86f72 | ||
|
|
5567a69142 | ||
|
|
95d43d936c | ||
|
|
9f56568680 | ||
|
|
762fec1d9a | ||
|
|
3428940c72 | ||
|
|
ea604a5e64 | ||
|
|
0946b0d3a3 | ||
|
|
5b76473cbc | ||
|
|
db5359e086 | ||
|
|
976dd2cbfb | ||
|
|
35046f11c2 | ||
|
|
f4c4005a2a | ||
|
|
b6956b3649 | ||
|
|
69d8d27ad3 | ||
|
|
6cf4a50a83 | ||
|
|
755acd24f0 | ||
|
|
b2b4902d23 | ||
|
|
079d6cfee6 | ||
|
|
6f69129829 | ||
|
|
3e3b3c269a | ||
|
|
39f1af7d4b | ||
|
|
1ea4d616d7 | ||
|
|
fdf0ecf6f6 | ||
|
|
d846894d22 | ||
|
|
7d3101c877 | ||
|
|
e5f7285253 | ||
|
|
e46c304f7c | ||
|
|
c3f0e6f10c | ||
|
|
1aa570db90 | ||
|
|
1c2be3c890 | ||
|
|
05e4eacf34 | ||
|
|
d0da2dd66c | ||
|
|
e1417b03f9 | ||
|
|
7b99de8046 | ||
|
|
877b3ab39c | ||
|
|
6dc03d7f67 | ||
|
|
2a5e6a51aa | ||
|
|
885477fa68 | ||
|
|
8f031a78c1 | ||
|
|
c7db792793 | ||
|
|
31be51ef79 | ||
|
|
e4066c1570 | ||
|
|
69b36a4c34 | ||
|
|
c7b8df5655 | ||
|
|
d85768b2ac | ||
|
|
ec480f2cb1 | ||
|
|
a569c1f4f9 | ||
|
|
f5b30c9376 | ||
|
|
412508fbcf | ||
|
|
dbfd9bce54 | ||
|
|
07a8e5b216 | ||
|
|
38bf58c613 | ||
|
|
ba90d72d55 | ||
|
|
9889bee924 | ||
|
|
a19e4e8f16 | ||
|
|
5121852fbc | ||
|
|
52e0558c79 | ||
|
|
1d1ff5d20b | ||
|
|
ec00697d31 | ||
|
|
c25714b68e | ||
|
|
562d0b8641 | ||
|
|
dc0147c5f9 | ||
|
|
296afdbeee | ||
|
|
d48d6cdd91 | ||
|
|
9363790541 | ||
|
|
2bceba948d | ||
|
|
2f8f058c5c | ||
|
|
168d4246af | ||
|
|
133d593689 | ||
|
|
cdf6050ec2 | ||
|
|
68784018e6 | ||
|
|
19dfec2a34 | ||
|
|
55d729339f | ||
|
|
c3108a17f4 | ||
|
|
d47ae64bd6 | ||
|
|
5206ba7a74 | ||
|
|
f451155689 | ||
|
|
9f06e19346 | ||
|
|
f4be3e9668 | ||
|
|
095e1e9789 | ||
|
|
a0b9f99dd3 | ||
|
|
a33a92207b | ||
|
|
ee613db0cb | ||
|
|
55b892a74e | ||
|
|
16754d9657 | ||
|
|
33db67122c | ||
|
|
b9b88b0e23 | ||
|
|
992ad71910 | ||
|
|
51d1f926c2 | ||
|
|
c70447f337 | ||
|
|
dc367e1a30 | ||
|
|
f647e1ff11 | ||
|
|
ee7997ffbc | ||
|
|
2dbb5adbbb | ||
|
|
cc9f342575 | ||
|
|
252262f4a7 | ||
|
|
f77a16648f | ||
|
|
d6e3c54b68 | ||
|
|
7294e96a77 | ||
|
|
a4f4f45251 | ||
|
|
acc91abc0c | ||
|
|
18d5176cb9 | ||
|
|
d6d6ced7a4 | ||
|
|
52809cc849 | ||
|
|
c525b7ea3f | ||
|
|
e799417aaf | ||
|
|
ef077c2d48 | ||
|
|
e78b114544 | ||
|
|
df170ddd27 | ||
|
|
17928741e3 | ||
|
|
a6b825ffdf | ||
|
|
0d3bc0e95f | ||
|
|
0f62a2d73d | ||
|
|
bcdd32f92f | ||
|
|
6865fcbffc | ||
|
|
8e623c71a9 | ||
|
|
1e06502464 | ||
|
|
f2914868e2 | ||
|
|
561f0313f9 | ||
|
|
1e27c4e1ae | ||
|
|
46676ba8eb | ||
|
|
02a49efbea | ||
|
|
5f2a5e1025 | ||
|
|
42552e4f4f | ||
|
|
f2af78f056 | ||
|
|
269d55bde3 | ||
|
|
71f3dbbeb4 | ||
|
|
73dfb74ed7 | ||
|
|
7dd6baef33 | ||
|
|
519b532091 | ||
|
|
4fb9c37c0d | ||
|
|
c9b63be29f | ||
|
|
84bc735dce | ||
|
|
a44b9417a1 | ||
|
|
6b157ed43c | ||
|
|
9050276ea7 | ||
|
|
683293d042 | ||
|
|
70d009ab49 | ||
|
|
dbc2d10d6d | ||
|
|
059b854db9 | ||
|
|
13569a1785 | ||
|
|
6fd70b165b | ||
|
|
11d94cf286 | ||
|
|
c98a48ea14 | ||
|
|
b8fa80419b | ||
|
|
2a8846297f | ||
|
|
fb322f760f | ||
|
|
0b9f718106 | ||
|
|
57b3b3a9cd | ||
|
|
6513432993 | ||
|
|
80a564d4b6 | ||
|
|
132ecffc40 | ||
|
|
cdf02a8eac | ||
|
|
7d22dcab7c | ||
|
|
45c943a78f | ||
|
|
cf82a8013f | ||
|
|
25d145d864 | ||
|
|
7822e3f90e | ||
|
|
a4a612fbe4 | ||
|
|
07da472e82 | ||
|
|
e9096632a4 | ||
|
|
6475a8bdce | ||
|
|
535f92b7d2 | ||
|
|
00b91616f5 | ||
|
|
b8c34762ed | ||
|
|
9f92347936 | ||
|
|
b657a893ad | ||
|
|
387670212a | ||
|
|
b8e42544ae | ||
|
|
bcea47c2c6 | ||
|
|
b38aaba56c | ||
|
|
8099607d90 | ||
|
|
2d9412e4e8 | ||
|
|
069d508528 | ||
|
|
d68ce67e4f | ||
|
|
a9d38e743f | ||
|
|
878e81dc8f | ||
|
|
5ccf74f99c | ||
|
|
6c823f4914 | ||
|
|
de35155ffe | ||
|
|
470eb19a2f | ||
|
|
947bc33946 | ||
|
|
8bcaabfb57 | ||
|
|
a1f7e7b755 | ||
|
|
c8f8a86a98 | ||
|
|
bb205af019 | ||
|
|
443c6046f9 | ||
|
|
1620b94a7b | ||
|
|
81f94eac6c | ||
|
|
ffada75d9e | ||
|
|
34d773bcd8 | ||
|
|
ec7509670d | ||
|
|
eceac11af2 | ||
|
|
dd64598850 | ||
|
|
650ace6be6 | ||
|
|
1c71a5c5e0 | ||
|
|
9c699a634b | ||
|
|
a47d17bbf5 | ||
|
|
164f67d6ce | ||
|
|
465b9e7177 | ||
|
|
f5a914c318 | ||
|
|
de486d2e73 | ||
|
|
d7f946ec2a | ||
|
|
4d1480bb61 | ||
|
|
25a49473f9 | ||
|
|
8e86e7fba5 | ||
|
|
f9b380a0d4 | ||
|
|
f664a56b16 | ||
|
|
0acfd3c4b1 | ||
|
|
bfc4cb1ad9 | ||
|
|
66dd1a9a2b | ||
|
|
1f42be2e54 | ||
|
|
30d344af6b | ||
|
|
3ca989fd8c | ||
|
|
73c38b3ae4 | ||
|
|
08749b0c92 | ||
|
|
ea73af371e | ||
|
|
ce2086a480 | ||
|
|
63f7106d2b | ||
|
|
8cce6ea6e3 | ||
|
|
77c486b4c9 | ||
|
|
7c8f276bb0 | ||
|
|
e991586254 | ||
|
|
d61ab796a7 | ||
|
|
e634369975 | ||
|
|
453236cf3a | ||
|
|
3530476b07 | ||
|
|
e75d8d1874 | ||
|
|
719fa6a67d | ||
|
|
148cd43c5d | ||
|
|
3d753a2d19 | ||
|
|
693d02de87 | ||
|
|
80b4ef3ae8 | ||
|
|
6b9269eb2d | ||
|
|
15c9529885 | ||
|
|
512cd70d13 | ||
|
|
e5599ff4c4 | ||
|
|
32041ee3fc | ||
|
|
07bf84ed69 | ||
|
|
0c0449aa17 | ||
|
|
26cb527d37 | ||
|
|
33c282051b | ||
|
|
df0f8ef9dc | ||
|
|
e23f13a1b3 | ||
|
|
f5b8bf1d74 | ||
|
|
61766017ea | ||
|
|
706984a53b | ||
|
|
e137feca20 | ||
|
|
9e66d7c929 | ||
|
|
9605bf5c21 | ||
|
|
c2cc7b1e9e | ||
|
|
fe55eccbf9 | ||
|
|
47e17dc78a | ||
|
|
88a71dd647 | ||
|
|
ce0b3a8635 | ||
|
|
bd3d86a6dd | ||
|
|
c3b395c99e | ||
|
|
eb4e5d9576 | ||
|
|
19a03ec080 | ||
|
|
33d5d9b462 | ||
|
|
b5a371d0cf | ||
|
|
b61fd17879 | ||
|
|
6722f8b1e5 | ||
|
|
69ff75157d | ||
|
|
7fad968ad2 | ||
|
|
7c619f5439 | ||
|
|
3b35f38ad5 | ||
|
|
096017c3d6 | ||
|
|
4ff2ab1bc8 | ||
|
|
ef698529d7 | ||
|
|
167d5bd89a | ||
|
|
c328a55453 | ||
|
|
83a976e98f | ||
|
|
d004133ad6 | ||
|
|
1f5c1b9658 | ||
|
|
56dfd174dd | ||
|
|
532cd4937b | ||
|
|
91bc73baf2 | ||
|
|
ab031c01de | ||
|
|
e51f72ff98 | ||
|
|
3eb010285d | ||
|
|
2b172f9999 | ||
|
|
0803f5af35 | ||
|
|
69ac2f0a6c | ||
|
|
0c842e0e15 | ||
|
|
d94678d4f4 | ||
|
|
90814f41a2 | ||
|
|
282dbd0913 | ||
|
|
1343b68cc6 | ||
|
|
ae07f71e76 | ||
|
|
54dc9c8587 | ||
|
|
9f9fa3b952 | ||
|
|
cc7c98336f | ||
|
|
dc22b96bed | ||
|
|
ae9e9f4b72 | ||
|
|
301c680bff | ||
|
|
595159f24d | ||
|
|
3bf1ec25c1 | ||
|
|
40c801592d | ||
|
|
9012e4deec | ||
|
|
ab2323e5c1 | ||
|
|
f31ae9ac6d | ||
|
|
27c24619c3 | ||
|
|
38aef71269 |
@@ -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
|
||||
0
.localstack/.gitkeep
Normal file
0
.localstack/.gitkeep
Normal file
@@ -9,10 +9,10 @@ const config = {
|
||||
arrowParens: "always",
|
||||
jsxSingleQuote: false,
|
||||
bracketSameLine: false,
|
||||
endOfLine: "lf",
|
||||
importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
|
||||
importOrderSeparation: true,
|
||||
importOrderSortSpecifiers: true
|
||||
endOfLine: "lf"
|
||||
// importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
|
||||
// importOrderSeparation: true,
|
||||
// importOrderSortSpecifiers: true
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
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>/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
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"]
|
||||
@@ -2,7 +2,7 @@ NGROK TEsting:
|
||||
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
|
||||
|
||||
Finding deadfiles - run from client directory
|
||||
npx deadfile ./src/index.js --exclude build templates
|
||||
npx deadfile ./src/index.jsx --exclude build templates
|
||||
|
||||
#Crushing all hasura migrations by creating a new initialization from the server.
|
||||
hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||
@@ -11,4 +11,4 @@ Production-ImEXOnline!@#'
|
||||
hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
|
||||
|
||||
Generate the license file:
|
||||
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite
|
||||
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite
|
||||
|
||||
64
_reference/dockerreadme.md
Normal file
64
_reference/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
|
||||
59
_reference/prHelper.html
Normal file
59
_reference/prHelper.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>
|
||||
41
_reference/productionBoardNotes.md
Normal file
41
_reference/productionBoardNotes.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Production Board Notes:
|
||||
|
||||
## General Notes
|
||||
|
||||
- You can single click the lane footer to collapse/un-collapse the lane
|
||||
- You can double click the lane header to collapse/un-collapse the lane
|
||||
- If you need to scroll horizontally, you can hold shift and use the mouse scroll wheel, or press the mouse scroll wheel while scrolling
|
||||
|
||||
## Board Settings
|
||||
|
||||
#### Layout
|
||||
|
||||
- Board Orientation (Vertical or Horizontal)
|
||||
- This determines the orientation of the card layout on the board.
|
||||
- Horizontal is the default setting, and how the prior board was set up.
|
||||
- Vertical is the new setting and allows lanes to be displayed vertically, with a grid of cards
|
||||
- Card Size (Small, Medium, Large)
|
||||
- This determines the size of the cards on the board.
|
||||
- Small is the default setting, and how the prior board was set up.
|
||||
- Medium and Large are new settings and allow for larger cards to be displayed on the board.
|
||||
- Compact Cards (Tall or Wide)
|
||||
- Formally called 'Compact'
|
||||
- When on, data is displayed on the card vertically
|
||||
- when turned off, some fields may share horizontal space, tightening the card layout
|
||||
- Colored Cards (On or Off)
|
||||
- When on, cards are colored based on the Status color
|
||||
- Kiosk Mode (On or Off)
|
||||
- This should be turned on if the shop is using it on a tablet (Ipad)
|
||||
|
||||
#### Information
|
||||
|
||||
These allow users to turn fields on or off, turning them all off will show the card in the most minimal form
|
||||
|
||||
|
||||
### Statistics
|
||||
|
||||
- The statistics section allows users to see accumulations of both jobs on the board, and jobs in production.
|
||||
- you can click a statistic to turn it on and off, and drag and drop the statistics to rearrange them
|
||||
|
||||
### Filters
|
||||
- Allows you to set, and persist filters for estimators and insurance companies
|
||||
@@ -4,7 +4,7 @@ Clone Repository for:
|
||||
{
|
||||
"name": "node-webhook-scripts",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"main": "index.jsx",
|
||||
"dependencies": {
|
||||
"express": "^4.16.4"
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ module.exports = {
|
||||
|
||||
{
|
||||
name: "Bitbucket Webhook",
|
||||
script: "./webhook/index.js",
|
||||
script: "./webhook/index.jsx",
|
||||
env: {
|
||||
NODE_ENV: "production"
|
||||
}
|
||||
|
||||
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,14 +1,14 @@
|
||||
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":"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='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
|
||||
|
||||
8
client/.eslintrc
Normal file
8
client/.eslintrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": [
|
||||
"react-app"
|
||||
],
|
||||
"rules": {
|
||||
"no-useless-rename": "off"
|
||||
}
|
||||
}
|
||||
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
/dev-dist
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
// craco.config.js
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const CracoLessPlugin = require("craco-less");
|
||||
const { convertLegacyToken } = require("@ant-design/compatible/lib");
|
||||
const { theme } = require("antd/lib");
|
||||
|
||||
const { defaultAlgorithm, defaultSeed } = theme;
|
||||
|
||||
const mapToken = defaultAlgorithm(defaultSeed);
|
||||
const v4Token = convertLegacyToken(mapToken);
|
||||
|
||||
// TODO, At the moment we are using less in the Dashboard. Once we remove this we can remove the less processor entirely.
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
plugin: CracoLessPlugin,
|
||||
options: {
|
||||
lessLoaderOptions: {
|
||||
lessOptions: {
|
||||
modifyVars: { ...v4Token },
|
||||
javascriptEnabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
webpack: {
|
||||
configure: (webpackConfig) => {
|
||||
return {
|
||||
...webpackConfig,
|
||||
// Required for Dev Server
|
||||
devServer: {
|
||||
...webpackConfig.devServer,
|
||||
allowedHosts: "all"
|
||||
},
|
||||
optimization: {
|
||||
...webpackConfig.optimization,
|
||||
// Workaround for CircleCI bug caused by the number of CPUs shown
|
||||
// https://github.com/facebook/create-react-app/issues/8320
|
||||
minimizer: webpackConfig.optimization.minimizer.map((item) => {
|
||||
if (item instanceof TerserPlugin) {
|
||||
item.options.parallel = 2;
|
||||
}
|
||||
|
||||
return item;
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
devtool: "source-map"
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
// This example plugins/index.jsx can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// This example support/index.jsx is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
|
||||
@@ -1,174 +1,126 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||
<link rel="icon" href="/favicon.png" />
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||
<link rel="icon" href="/ro-favicon.png" />
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||
<link rel="icon" href="/pm/pm-favicon.ico" />
|
||||
<% } %>
|
||||
<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') { %>
|
||||
<link rel="icon" href="/ro-favicon.png"/>
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||
<link rel="icon" href="/pm/pm-favicon.ico"/>
|
||||
<% } %>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<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="/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
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||
<meta name="description" content="ImEX Online" />
|
||||
<title>ImEX Online</title>
|
||||
<script type="text/javascript">
|
||||
window.$crisp = [];
|
||||
window.CRISP_WEBSITE_ID = '36724f62-2eb0-4b29-9cdd-9905fb99913e';
|
||||
(function () {
|
||||
d = document;
|
||||
s = d.createElement('script');
|
||||
s.src = 'https://client.crisp.chat/l.js';
|
||||
s.async = 1;
|
||||
d.getElementsByTagName('head')[0].appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||
<meta name="description" content="Rome Online" />
|
||||
<title>Rome Online</title>
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||
<meta name="description" content="ImEX Online"/>
|
||||
<title>ImEX Online</title>
|
||||
<script type="text/javascript">
|
||||
window.$crisp = [];
|
||||
window.CRISP_WEBSITE_ID = '36724f62-2eb0-4b29-9cdd-9905fb99913e';
|
||||
(function () {
|
||||
d = document;
|
||||
s = d.createElement('script');
|
||||
s.src = 'https://client.crisp.chat/l.js';
|
||||
s.async = 1;
|
||||
d.getElementsByTagName('head')[0].appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||
<meta name="description" content="Rome Online"/>
|
||||
<title>Rome Online</title>
|
||||
<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>
|
||||
|
||||
<!--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-->
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||
<title>ProManager</title>
|
||||
<meta name="description" content="ProManager"/>
|
||||
|
||||
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001 party="LiveChat528346"></call-us-selector>
|
||||
<% } %>
|
||||
<script>
|
||||
!(function () {
|
||||
'use strict';
|
||||
var e = [
|
||||
'debug',
|
||||
'destroy',
|
||||
'do',
|
||||
'help',
|
||||
'identify',
|
||||
'is',
|
||||
'off',
|
||||
'on',
|
||||
'ready',
|
||||
'render',
|
||||
'reset',
|
||||
'safe',
|
||||
'set',
|
||||
];
|
||||
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
|
||||
else {
|
||||
var n = (window.noticeable = window.noticeable || []);
|
||||
|
||||
<!--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>
|
||||
|
||||
|
||||
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||
<title>ProManager</title>
|
||||
<meta name="description" content="ProManager" />
|
||||
|
||||
<% } %>
|
||||
<script>
|
||||
!(function () {
|
||||
'use strict';
|
||||
var e = [
|
||||
'debug',
|
||||
'destroy',
|
||||
'do',
|
||||
'help',
|
||||
'identify',
|
||||
'is',
|
||||
'off',
|
||||
'on',
|
||||
'ready',
|
||||
'render',
|
||||
'reset',
|
||||
'safe',
|
||||
'set',
|
||||
];
|
||||
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
|
||||
else {
|
||||
var n = (window.noticeable = window.noticeable || []);
|
||||
function t(e) {
|
||||
return function () {
|
||||
var t = Array.prototype.slice.call(arguments);
|
||||
return t.unshift(e), n.push(t), n;
|
||||
};
|
||||
}
|
||||
!(function () {
|
||||
for (var o = 0; o < e.length; o++) {
|
||||
var r = e[o];
|
||||
n[r] = t(r);
|
||||
}
|
||||
})(),
|
||||
(function () {
|
||||
var e = document.createElement('script');
|
||||
(e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js');
|
||||
var n = document.head;
|
||||
n.insertBefore(e, n.firstChild);
|
||||
})();
|
||||
function t(e) {
|
||||
return function () {
|
||||
var t = Array.prototype.slice.call(arguments);
|
||||
return t.unshift(e), n.push(t), n;
|
||||
};
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module" src="src/index.jsx"></script>
|
||||
</body>
|
||||
!(function () {
|
||||
for (var o = 0; o < e.length; o++) {
|
||||
var r = e[o];
|
||||
n[r] = t(r);
|
||||
}
|
||||
})(),
|
||||
(function () {
|
||||
var e = document.createElement('script');
|
||||
(e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js');
|
||||
var n = document.head;
|
||||
n.insertBefore(e, n.firstChild);
|
||||
})();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module" src="src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
24209
client/package-lock.json
generated
24209
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,93 +2,98 @@
|
||||
"name": "bodyshop",
|
||||
"version": "0.2.1",
|
||||
"engines": {
|
||||
"node": "18.18.2"
|
||||
"node": ">=18.18.2"
|
||||
},
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@ant-design/compatible": "^5.1.2",
|
||||
"@ant-design/pro-layout": "^7.17.16",
|
||||
"@apollo/client": "^3.8.10",
|
||||
"@asseinfo/react-kanban": "^2.2.0",
|
||||
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
||||
"@ant-design/pro-layout": "^7.19.12",
|
||||
"@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.1",
|
||||
"@sentry/cli": "^2.28.6",
|
||||
"@sentry/react": "^7.104.0",
|
||||
"@splitsoftware/splitio-react": "^1.11.0",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"@sentry/cli": "^2.36.2",
|
||||
"@sentry/react": "^7.114.0",
|
||||
"@splitsoftware/splitio-react": "^1.13.0",
|
||||
"@tanem/react-nprogress": "^5.0.51",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"antd": "^5.15.3",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"antd": "^5.20.1",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"apollo-link-sentry": "^3.3.0",
|
||||
"axios": "^1.6.7",
|
||||
"dayjs": "^1.11.10",
|
||||
"autosize": "^6.0.1",
|
||||
"axios": "^1.7.7",
|
||||
"classnames": "^2.5.1",
|
||||
"css-box-model": "^1.2.1",
|
||||
"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.8.1",
|
||||
"graphql": "^16.6.0",
|
||||
"i18next": "^23.10.0",
|
||||
"i18next-browser-languagedetector": "^7.0.2",
|
||||
"libphonenumber-js": "^1.10.57",
|
||||
"logrocket": "^8.0.1",
|
||||
"markerjs2": "^2.32.0",
|
||||
"normalize-url": "^8.0.0",
|
||||
"firebase": "^10.13.2",
|
||||
"graphql": "^16.9.0",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"libphonenumber-js": "^1.11.9",
|
||||
"logrocket": "^8.1.2",
|
||||
"markerjs2": "^2.32.2",
|
||||
"memoize-one": "^6.0.0",
|
||||
"normalize-url": "^8.0.1",
|
||||
"object-hash": "^3.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^9.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-big-calendar": "^1.11.0",
|
||||
"query-string": "^9.1.0",
|
||||
"raf-schd": "^4.0.3",
|
||||
"react": "^18.3.1",
|
||||
"react-big-calendar": "^1.14.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^7.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-cookie": "^7.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-grid-gallery": "^1.0.0",
|
||||
"react-grid-gallery": "^1.0.1",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-i18next": "^14.0.5",
|
||||
"react-icons": "^5.0.1",
|
||||
"react-i18next": "^14.1.3",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-joyride": "^2.7.4",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-number-format": "^5.3.3",
|
||||
"react-product-fruits": "^2.2.6",
|
||||
"react-redux": "^9.1.0",
|
||||
"react-number-format": "^5.4.2",
|
||||
"react-popopo": "^2.1.9",
|
||||
"react-product-fruits": "^2.2.61",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-resizable": "^3.0.5",
|
||||
"react-router-dom": "^6.22.2",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-router-dom": "^6.26.2",
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-virtualized": "^9.22.5",
|
||||
"recharts": "^2.12.2",
|
||||
"react-virtuoso": "^4.10.4",
|
||||
"recharts": "^2.12.7",
|
||||
"redux": "^5.0.1",
|
||||
"redux-actions": "^3.0.3",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.0",
|
||||
"sass": "^1.71.1",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"styled-components": "^6.1.8",
|
||||
"reselect": "^5.1.1",
|
||||
"sass": "^1.79.3",
|
||||
"socket.io-client": "^4.8.0",
|
||||
"styled-components": "^6.1.13",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"userpilot": "^1.3.1",
|
||||
"use-memo-one": "^1.1.3",
|
||||
"userpilot": "^1.3.6",
|
||||
"vite-plugin-ejs": "^1.7.0",
|
||||
"web-vitals": "^3.5.2",
|
||||
"workbox-core": "^7.0.0",
|
||||
"workbox-expiration": "^7.0.0",
|
||||
"workbox-navigation-preload": "^7.0.0",
|
||||
"workbox-precaching": "^7.0.0",
|
||||
"workbox-routing": "^7.0.0",
|
||||
"workbox-strategies": "^7.0.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": "vite build",
|
||||
"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",
|
||||
@@ -128,29 +133,31 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-react": "^7.23.3",
|
||||
"@dotenvx/dotenvx": "^0.15.4",
|
||||
"@emotion/babel-plugin": "^11.11.0",
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@sentry/webpack-plugin": "^2.14.2",
|
||||
"@swc/core": "^1.3.107",
|
||||
"@swc/plugin-styled-components": "^1.5.108",
|
||||
"@testing-library/cypress": "^10.0.1",
|
||||
"browserslist": "^4.22.3",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@dotenvx/dotenvx": "^1.14.1",
|
||||
"@emotion/babel-plugin": "^11.12.0",
|
||||
"@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.6.6",
|
||||
"cypress": "^13.14.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"memfs": "^4.6.0",
|
||||
"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.0.11",
|
||||
"vite": "^5.4.7",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.19.0",
|
||||
"vite-plugin-pwa": "^0.19.0",
|
||||
"vite-plugin-style-import": "^2.0.0"
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-pwa": "^0.20.5",
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"workbox-window": "^7.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16422,7 +16422,7 @@ For when you don't want to write the same thing over and over to cache a method
|
||||
$ npm install --save-dev stubs
|
||||
```
|
||||
```js
|
||||
var mylib = require('./lib/index.js')
|
||||
var mylib = require('./lib/index.jsx')
|
||||
var stubs = require('stubs')
|
||||
|
||||
// make it a noop
|
||||
|
||||
@@ -16567,7 +16567,7 @@ even more slower.
|
||||
## Benchmarks
|
||||
|
||||
```bash
|
||||
$ node benchmarks/index.js
|
||||
$ node benchmarks/index.jsx
|
||||
Benchmarking: sign
|
||||
elliptic#sign x 262 ops/sec ±0.51% (177 runs sampled)
|
||||
eccjs#sign x 55.91 ops/sec ±0.90% (144 runs sampled)
|
||||
|
||||
56
client/public/firebase-messaging-sw.js
Normal file
56
client/public/firebase-messaging-sw.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// Scripts for firebase and firebase messaging
|
||||
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
|
||||
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
|
||||
|
||||
// Initialize the Firebase app in the service worker by passing the generated config
|
||||
let firebaseConfig;
|
||||
switch (this.location.hostname) {
|
||||
case "localhost":
|
||||
firebaseConfig = {
|
||||
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",
|
||||
};
|
||||
break;
|
||||
case "test.imex.online":
|
||||
firebaseConfig = {
|
||||
apiKey: "AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c",
|
||||
authDomain: "imex-test.firebaseapp.com",
|
||||
projectId: "imex-test",
|
||||
storageBucket: "imex-test.appspot.com",
|
||||
messagingSenderId: "991923618608",
|
||||
appId: "1:991923618608:web:633437569cdad78299bef5",
|
||||
// measurementId: "${config.measurementId}",
|
||||
};
|
||||
break;
|
||||
case "imex.online":
|
||||
default:
|
||||
firebaseConfig = {
|
||||
apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU",
|
||||
authDomain: "imex-prod.firebaseapp.com",
|
||||
databaseURL: "https://imex-prod.firebaseio.com",
|
||||
projectId: "imex-prod",
|
||||
storageBucket: "imex-prod.appspot.com",
|
||||
messagingSenderId: "253497221485",
|
||||
appId: "1:253497221485:web:3c81c483b94db84b227a64",
|
||||
measurementId: "G-NTWBKG2L0M",
|
||||
};
|
||||
}
|
||||
|
||||
firebase.initializeApp(firebaseConfig);
|
||||
|
||||
// Retrieve firebase messaging
|
||||
const messaging = firebase.messaging();
|
||||
|
||||
messaging.onBackgroundMessage(function (payload) {
|
||||
// Customize notification here
|
||||
const channel = new BroadcastChannel("imex-sw-messages");
|
||||
channel.postMessage(payload);
|
||||
|
||||
//self.registration.showNotification(notificationTitle, notificationOptions);
|
||||
});
|
||||
@@ -7,9 +7,7 @@ import { connect } from "react-redux";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||
|
||||
//Component Imports
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component"; // Component Imports
|
||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
||||
import LandingPage from "../pages/landing/landing.page";
|
||||
@@ -20,23 +18,24 @@ 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/betaHandler";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
import { ProductFruits } from "react-product-fruits";
|
||||
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"));
|
||||
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
||||
|
||||
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
||||
const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container"));
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
online: selectOnline,
|
||||
bodyshop: selectBodyshop,
|
||||
currentEula: selectCurrentEula
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
checkUserSession: () => dispatch(checkUserSession()),
|
||||
setOnline: (isOnline) => dispatch(setOnline(isOnline))
|
||||
@@ -60,11 +59,11 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
|
||||
// Associate event listeners, memoize to prevent multiple listeners being added
|
||||
useEffect(() => {
|
||||
const offlineListener = (e) => {
|
||||
const offlineListener = () => {
|
||||
setOnline(false);
|
||||
};
|
||||
|
||||
const onlineListener = (e) => {
|
||||
const onlineListener = () => {
|
||||
setOnline(true);
|
||||
};
|
||||
|
||||
@@ -98,7 +97,7 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
InstanceRenderMgr({
|
||||
imex: "gvfvfw/bodyshopapp",
|
||||
rome: "rome-online/rome-online",
|
||||
promanager: "" //TODO:AIO Add in log rocket for promanager instances.
|
||||
promanager: "" // TODO: AIO Add in log rocket for promanager instances.
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -109,26 +108,20 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
||||
}
|
||||
|
||||
handleBeta();
|
||||
|
||||
if (!online)
|
||||
if (!online) {
|
||||
return (
|
||||
<Result
|
||||
status="warning"
|
||||
title={t("general.labels.nointernet")}
|
||||
subTitle={t("general.labels.nointernet_sub")}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
<Button type="primary" onClick={() => window.location.reload()}>
|
||||
{t("general.actions.refresh")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentEula && !currentUser.eulaIsAccepted) {
|
||||
return <Eula />;
|
||||
@@ -147,18 +140,13 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ProductFruits
|
||||
<ProductFruitsWrapper
|
||||
currentUser={currentUser}
|
||||
workspaceCode={InstanceRenderMgr({
|
||||
imex: null,
|
||||
rome: null,
|
||||
rome: "9BkbEseqNqxw8jUH",
|
||||
promanager: "aoJoEifvezYI0Z0P"
|
||||
})}
|
||||
debug
|
||||
language="en"
|
||||
user={{
|
||||
email: currentUser.email,
|
||||
username: currentUser.email
|
||||
}}
|
||||
/>
|
||||
|
||||
<Routes>
|
||||
@@ -214,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>
|
||||
}
|
||||
>
|
||||
@@ -224,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>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -161,3 +161,15 @@
|
||||
.rowWithColor > td {
|
||||
background-color: var(--bgColor) !important;
|
||||
}
|
||||
|
||||
.muted-button {
|
||||
color: lightgray;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px; /* Adjust as needed */
|
||||
}
|
||||
|
||||
.muted-button:hover {
|
||||
color: darkgrey;
|
||||
}
|
||||
|
||||
32
client/src/App/ProductFruitsWrapper.jsx
Normal file
32
client/src/App/ProductFruitsWrapper.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
import { ProductFruits } from "react-product-fruits";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const ProductFruitsWrapper = React.memo(({ currentUser, workspaceCode }) => {
|
||||
return (
|
||||
workspaceCode &&
|
||||
currentUser?.authorized === true &&
|
||||
currentUser?.email && (
|
||||
<ProductFruits
|
||||
lifeCycle="unmount"
|
||||
workspaceCode={workspaceCode}
|
||||
debug
|
||||
language="en"
|
||||
user={{
|
||||
email: currentUser.email,
|
||||
username: currentUser.email
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
export default ProductFruitsWrapper;
|
||||
|
||||
ProductFruitsWrapper.propTypes = {
|
||||
currentUser: PropTypes.shape({
|
||||
authorized: PropTypes.bool,
|
||||
email: PropTypes.string
|
||||
}),
|
||||
workspaceCode: PropTypes.string
|
||||
};
|
||||
@@ -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,19 @@
|
||||
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";
|
||||
|
||||
@@ -170,13 +171,22 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
||||
extra={
|
||||
<Space wrap>
|
||||
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
|
||||
<JobsExportAllButton
|
||||
jobIds={selectedJobs}
|
||||
disabled={transInProgress || selectedJobs.length === 0}
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedJobs}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<>
|
||||
<JobMarkSelectedExported
|
||||
jobIds={selectedJobs}
|
||||
disabled={transInProgress || selectedJobs.length === 0}
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedJobs}
|
||||
refetch={refetch}
|
||||
/>
|
||||
<JobsExportAllButton
|
||||
jobIds={selectedJobs}
|
||||
disabled={transInProgress || selectedJobs.length === 0}
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedJobs}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />}
|
||||
<Input.Search
|
||||
@@ -191,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}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -13,6 +13,7 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import dayjs from "../../utils/day";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||
@@ -22,7 +23,6 @@ import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-galler
|
||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import BillDetailEditReturn from "./bill-detail-edit-return.component";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -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) {
|
||||
@@ -153,6 +153,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||
|
||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
||||
const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -188,7 +189,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
}
|
||||
/>
|
||||
<Form form={form} onFinish={handleFinish} initialValues={transformData(data)} layout="vertical">
|
||||
<BillFormContainer form={form} billEdit disabled={exported} />
|
||||
<BillFormContainer form={form} billEdit disabled={exported} disableInHouse={isinhouse} />
|
||||
<Divider orientation="left">{t("general.labels.media")}</Divider>
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery
|
||||
|
||||
@@ -34,6 +34,7 @@ export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, b
|
||||
actions: {},
|
||||
context: {
|
||||
jobId: data.bills_by_pk.jobid,
|
||||
job: data.bills_by_pk.job,
|
||||
vendorId: data.bills_by_pk.vendorid,
|
||||
returnFromBill: data.bills_by_pk.id,
|
||||
invoiceNumber: data.bills_by_pk.invoice_number,
|
||||
|
||||
@@ -14,7 +14,6 @@ import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||
@@ -22,6 +21,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||
import BillFormLines from "./bill-form.lines.component";
|
||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -41,7 +41,8 @@ export function BillFormComponent({
|
||||
job,
|
||||
loadOutstandingReturns,
|
||||
loadInventory,
|
||||
preferredMake
|
||||
preferredMake,
|
||||
disableInHouse
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -177,7 +178,7 @@ export function BillFormComponent({
|
||||
]}
|
||||
>
|
||||
<VendorSearchSelect
|
||||
disabled={disabled}
|
||||
disabled={disabled || disableInHouse}
|
||||
options={vendorAutoCompleteOptions}
|
||||
preferredMake={preferredMake}
|
||||
onSelect={handleVendorSelect}
|
||||
@@ -243,7 +244,7 @@ export function BillFormComponent({
|
||||
})
|
||||
]}
|
||||
>
|
||||
<Input disabled={disabled || disableInvNumber} />
|
||||
<Input disabled={disabled || disableInvNumber || disableInHouse} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bills.fields.date")}
|
||||
@@ -275,7 +276,7 @@ export function BillFormComponent({
|
||||
})
|
||||
]}
|
||||
>
|
||||
<FormDatePicker disabled={disabled} />
|
||||
<DateTimePicker isDateOnly disabled={disabled} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bills.fields.is_credit_memo")}
|
||||
|
||||
@@ -16,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber }) {
|
||||
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse }) {
|
||||
const {
|
||||
treatments: { Simple_Inventory }
|
||||
} = useSplitTreatments({
|
||||
@@ -47,6 +47,7 @@ export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableI
|
||||
job={lineData ? lineData.jobs_by_pk : null}
|
||||
responsibilityCenters={bodyshop.md_responsibility_centers || null}
|
||||
disableInvNumber={disableInvNumber}
|
||||
disableInHouse={disableInHouse}
|
||||
loadOutstandingReturns={loadOutstandingReturns}
|
||||
loadInventory={loadInventory}
|
||||
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
||||
|
||||
@@ -3,8 +3,6 @@ import React, { forwardRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InstanceRenderMgr from "../../utils/instanceRenderMgr";
|
||||
|
||||
//To be used as a form element only.
|
||||
const { Option } = Select;
|
||||
const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -25,31 +23,27 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
);
|
||||
}}
|
||||
notFoundContent={"Removed."}
|
||||
{...restProps}
|
||||
>
|
||||
<Select.Option key={null} value={"noline"} cost={0} line_desc={""}>
|
||||
{t("billlines.labels.other")}
|
||||
</Select.Option>
|
||||
{options
|
||||
? options.map((item) => (
|
||||
<Option
|
||||
disabled={allowRemoved ? false : item.removed}
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
cost={item.act_price ? item.act_price : 0}
|
||||
part_type={item.part_type}
|
||||
line_desc={item.line_desc}
|
||||
part_qty={item.part_qty}
|
||||
oem_partno={item.oem_partno}
|
||||
alt_partno={item.alt_partno}
|
||||
act_price={item.act_price}
|
||||
style={{
|
||||
...(item.removed ? { textDecoration: "line-through" } : {})
|
||||
}}
|
||||
name={`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
|
||||
>
|
||||
options={[
|
||||
{ value: "noline", label: t("billlines.labels.other"), name: t("billlines.labels.other") },
|
||||
...options.map((item) => ({
|
||||
disabled: allowRemoved ? false : item.removed,
|
||||
key: item.id,
|
||||
value: item.id,
|
||||
cost: item.act_price ? item.act_price : 0,
|
||||
part_type: item.part_type,
|
||||
line_desc: item.line_desc,
|
||||
part_qty: item.part_qty,
|
||||
oem_partno: item.oem_partno,
|
||||
alt_partno: item.alt_partno,
|
||||
act_price: item.act_price,
|
||||
style: {
|
||||
...(item.removed ? { textDecoration: "line-through" } : {})
|
||||
},
|
||||
name: `${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
|
||||
label: (
|
||||
<>
|
||||
<span>
|
||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
@@ -60,14 +54,15 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>{`${item.mod_lb_hrs} units`}</span>
|
||||
)
|
||||
})}
|
||||
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
|
||||
</span>
|
||||
</Option>
|
||||
))
|
||||
: null}
|
||||
</Select>
|
||||
</>
|
||||
)
|
||||
}))
|
||||
]}
|
||||
{...restProps}
|
||||
></Select>
|
||||
);
|
||||
};
|
||||
export default forwardRef(BillLineSearchSelect);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
|
||||
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
import { FaTasks } from "react-icons/fa";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -21,9 +22,21 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
setReconciliationContext: (context) => dispatch(setModalContext({ context: context, modal: "reconciliation" }))
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
setReconciliationContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "reconciliation"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
export function BillsListTableComponent({
|
||||
@@ -32,9 +45,9 @@ export function BillsListTableComponent({
|
||||
job,
|
||||
billsQuery,
|
||||
handleOnRowClick,
|
||||
setPartsOrderContext,
|
||||
setBillEnterContext,
|
||||
setReconciliationContext
|
||||
setReconciliationContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -48,6 +61,7 @@ export function BillsListTableComponent({
|
||||
const Templates = TemplateList("bill");
|
||||
const bills = billsQuery.data ? billsQuery.data.bills : [];
|
||||
const { refetch } = billsQuery;
|
||||
|
||||
const recordActions = (record, showView = false) => (
|
||||
<Space wrap>
|
||||
{showView && (
|
||||
@@ -55,9 +69,22 @@ export function BillsListTableComponent({
|
||||
<EditFilled />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
title={t("tasks.buttons.create")}
|
||||
onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
billid: record.id
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaTasks />
|
||||
</Button>
|
||||
<BillDeleteButton bill={record} jobid={job.id} />
|
||||
<BillDetailEditReturnComponent
|
||||
data={{ bills_by_pk: { ...record, jobid: job.id } }}
|
||||
data={{ bills_by_pk: { ...record, jobid: job.id, job: job } }}
|
||||
disabled={record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid || jobRO}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
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, notification, Row, Space, Spin, Statistic } from "antd";
|
||||
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, message, notification } from "antd";
|
||||
import axios from "axios";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_PAYMENT_RESPONSE, QUERY_RO_AND_OWNER_BY_JOB_PKS } from "../../graphql/payment_response.queries";
|
||||
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||
@@ -16,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) => ({
|
||||
@@ -27,22 +27,27 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
||||
});
|
||||
|
||||
const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisible, insertAuditTrail }) => {
|
||||
const { context } = cardPaymentModal;
|
||||
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 [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [, { data, refetch, queryLoading }] = useLazyQuery(QUERY_RO_AND_OWNER_BY_JOB_PKS, {
|
||||
variables: { jobids: [context.jobid] },
|
||||
skip: true
|
||||
skip: !context?.jobid
|
||||
});
|
||||
|
||||
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
|
||||
//Initialize the intellipay window.
|
||||
const SetIntellipayCallbackFunctions = () => {
|
||||
console.log("*** Set IntelliPay callback functions.");
|
||||
@@ -51,16 +56,19 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
});
|
||||
|
||||
window.intellipay.runOnApproval(async function (response) {
|
||||
console.warn("*** Running On Approval Script ***");
|
||||
form.setFieldValue("paymentResponse", response);
|
||||
form.submit();
|
||||
//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();
|
||||
setLoading(false);
|
||||
toggleModalVisible();
|
||||
}, 750);
|
||||
});
|
||||
|
||||
window.intellipay.runOnNonApproval(async function (response) {
|
||||
// Mutate unsuccessful payment
|
||||
|
||||
const { payments } = form.getFieldsValue();
|
||||
|
||||
await insertPaymentResponse({
|
||||
variables: {
|
||||
paymentResponse: payments.map((payment) => ({
|
||||
@@ -85,50 +93,8 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
});
|
||||
};
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
try {
|
||||
await insertPayment({
|
||||
variables: {
|
||||
paymentInput: values.payments.map((payment) => ({
|
||||
amount: payment.amount,
|
||||
transactionid: (values.paymentResponse.paymentid || "").toString(),
|
||||
payer: t("payments.labels.customer"),
|
||||
type: values.paymentResponse.cardbrand,
|
||||
jobid: payment.jobid,
|
||||
date: dayjs(Date.now()),
|
||||
payment_responses: {
|
||||
data: [
|
||||
{
|
||||
amount: payment.amount,
|
||||
bodyshopid: bodyshop.id,
|
||||
|
||||
jobid: payment.jobid,
|
||||
declinereason: values.paymentResponse.declinereason,
|
||||
ext_paymentid: values.paymentResponse.paymentid.toString(),
|
||||
successful: true,
|
||||
response: values.paymentResponse
|
||||
}
|
||||
]
|
||||
}
|
||||
}))
|
||||
},
|
||||
refetchQueries: ["GET_JOB_BY_PK"]
|
||||
});
|
||||
toggleModalVisible();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("payments.errors.inserting", { error: error.message })
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIntelliPayCharge = async () => {
|
||||
setLoading(true);
|
||||
|
||||
//Validate
|
||||
try {
|
||||
await form.validateFields();
|
||||
@@ -140,7 +106,8 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
try {
|
||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||
bodyshop,
|
||||
refresh: !!window.intellipay
|
||||
refresh: !!window.intellipay,
|
||||
paymentSplitMeta: form.getFieldsValue()
|
||||
});
|
||||
|
||||
if (window.intellipay) {
|
||||
@@ -165,11 +132,46 @@ 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}>
|
||||
<Form
|
||||
onFinish={handleFinish}
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
@@ -242,22 +244,16 @@ 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()
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
console.log("Updating the owner info section.");
|
||||
//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) {
|
||||
console.log("**Calling refetch.");
|
||||
refetch({ jobids: payments.map((p) => p.jobid) });
|
||||
}
|
||||
console.log(
|
||||
"Acc info",
|
||||
data,
|
||||
payments && data && data.jobs.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
@@ -290,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} />
|
||||
@@ -300,6 +295,13 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
type="hidden"
|
||||
value={totalAmountToCharge?.toFixed(2)}
|
||||
/>
|
||||
<Input
|
||||
className="ipayfield"
|
||||
data-ipayname="comment"
|
||||
type="hidden"
|
||||
value={btoa(JSON.stringify(payments))}
|
||||
hidden
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
// data-ipayname="submit"
|
||||
@@ -310,16 +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>
|
||||
|
||||
{/* Lightbox payment response when it is completed */}
|
||||
<Form.Item name="paymentResponse" hidden>
|
||||
<Input type="hidden" />
|
||||
</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>
|
||||
);
|
||||
|
||||
@@ -8,8 +8,8 @@ import { DateFormatter } from "../../utils/DateFormatter";
|
||||
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 FormDatePicker from "../form-date-picker/form-date-picker.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 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";
|
||||
@@ -196,7 +196,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
{dlExpiresBeforeReturn && (
|
||||
<Space style={{ color: "tomato" }}>
|
||||
@@ -274,7 +274,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
||||
<InputPhone />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("contracts.fields.driver_dob")} name="driver_dob">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<ContractsRatesChangeButton form={form} />
|
||||
|
||||
@@ -10,16 +10,12 @@ import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
|
||||
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function CourtesyCarCreateFormComponent({
|
||||
form,
|
||||
saveLoading,
|
||||
newCC,
|
||||
}) {
|
||||
export default function CourtesyCarCreateFormComponent({ form, saveLoading, newCC }) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
|
||||
@@ -161,16 +157,16 @@ export default function CourtesyCarCreateFormComponent({
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("courtesycars.fields.purchasedate")} name="purchasedate">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("courtesycars.fields.servicestartdate")} name="servicestartdate">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("courtesycars.fields.serviceenddate")} name="serviceenddate">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("courtesycars.fields.leaseenddate")} name="leaseenddate">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
@@ -228,7 +224,7 @@ export default function CourtesyCarCreateFormComponent({
|
||||
</div>
|
||||
<div>
|
||||
<Form.Item label={t("courtesycars.fields.nextservicedate")} name="nextservicedate">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}>
|
||||
{() => {
|
||||
@@ -260,7 +256,7 @@ export default function CourtesyCarCreateFormComponent({
|
||||
</Form.Item>
|
||||
<div>
|
||||
<Form.Item label={t("courtesycars.fields.registrationexpires")} name="registrationexpires">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate={(p, c) => p.registrationexpires !== c.registrationexpires}>
|
||||
{() => {
|
||||
@@ -293,7 +289,7 @@ export default function CourtesyCarCreateFormComponent({
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}>
|
||||
{() => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Form, InputNumber } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function CourtesyCarReturnModalComponent() {
|
||||
const { t } = useTranslation();
|
||||
@@ -19,7 +19,7 @@ export default function CourtesyCarReturnModalComponent() {
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.kmend")}
|
||||
|
||||
@@ -28,6 +28,7 @@ const CourtesyCarStatusComponent = ({ value, onChange }, ref) => {
|
||||
<Option value="courtesycars.status.out">{t("courtesycars.status.out")}</Option>
|
||||
<Option value="courtesycars.status.sold">{t("courtesycars.status.sold")}</Option>
|
||||
<Option value="courtesycars.status.leasereturn">{t("courtesycars.status.leasereturn")}</Option>
|
||||
<Option value="courtesycars.status.unavailable">{t("courtesycars.status.unavailable")}</Option>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -61,7 +61,11 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
{
|
||||
text: t("courtesycars.status.leasereturn"),
|
||||
value: "courtesycars.status.leasereturn"
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t("courtesycars.status.unavailable"),
|
||||
value: "courtesycars.status.unavailable",
|
||||
},
|
||||
],
|
||||
onFilter: (value, record) => record.status === value,
|
||||
sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
|
||||
@@ -9,7 +9,6 @@ import axios from "axios";
|
||||
const fortyFiveDaysAgo = () => dayjs().subtract(45, "day").toLocaleString();
|
||||
|
||||
export default function JobLifecycleDashboardComponent({ data, bodyshop, ...cardProps }) {
|
||||
console.log("🚀 ~ JobLifecycleDashboardComponent ~ bodyshop:", bodyshop);
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [lifecycleData, setLifecycleData] = useState(null);
|
||||
@@ -143,7 +142,7 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
||||
>
|
||||
<div>
|
||||
{lifecycleData.summations.map((key) => (
|
||||
<Tag color={key.color} style={{ width: "13vh", padding: "4px", margin: "4px" }}>
|
||||
<Tag key={key.status} color={key.color} style={{ width: "13vh", padding: "4px", margin: "4px" }}>
|
||||
<div
|
||||
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||
@@ -165,6 +164,7 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
||||
size="small"
|
||||
pagination={false}
|
||||
columns={columns}
|
||||
rowKey={(record) => record.status}
|
||||
dataSource={lifecycleData.summations.sort((a, b) => b.value - a.value).slice(0, 3)}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@@ -36,9 +36,6 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
};
|
||||
});
|
||||
|
||||
console.log("Scheduled Out Today");
|
||||
console.dir(scheduledOutToday);
|
||||
|
||||
const tvFontSize = 18;
|
||||
const tvFontWeight = "bold";
|
||||
|
||||
@@ -89,8 +86,6 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder: state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
console.log("Render record out today");
|
||||
console.dir(record);
|
||||
return record.ownerid ? (
|
||||
<Link to={"/manage/owners/" + record.ownerid} onClick={(e) => e.stopPropagation()}>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
Typography
|
||||
} from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -22,11 +21,12 @@ import { createStructuredSelector } from "reselect";
|
||||
import { determineDmsType } from "../../pages/dms/dms.container";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import i18n from "../../translations/i18n";
|
||||
import dayjs from "../../utils/day";
|
||||
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
||||
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -89,7 +89,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
job.area_of_damage && job.area_of_damage.impact1
|
||||
? " " +
|
||||
t("jobs.labels.dms.damageto", {
|
||||
area_of_damage: (job.area_of_damage && job.area_of_damage.impact1) || "UNKNOWN"
|
||||
area_of_damage: (job.area_of_damage && job.area_of_damage.impact1.padStart(2, "0")) || "UNKNOWN"
|
||||
})
|
||||
: ""
|
||||
}`.slice(0, 239),
|
||||
@@ -164,7 +164,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item name="inservicedate" label={t("jobs.fields.dms.inservicedate")}>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Space>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Select, Space, Tag } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { Option } = Select;
|
||||
//To be used as a form element only.
|
||||
|
||||
const EmployeeSearchSelectEmail = ({ options, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
// value={option}
|
||||
style={{
|
||||
width: 400
|
||||
}}
|
||||
optionFilterProp="search"
|
||||
{...props}
|
||||
>
|
||||
{options
|
||||
? options.map((o) => (
|
||||
<Option key={o.id} value={o.user_email} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
|
||||
<Space>
|
||||
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
||||
|
||||
<Tag color="green">
|
||||
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
||||
</Tag>
|
||||
</Space>
|
||||
</Option>
|
||||
))
|
||||
: null}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
export default EmployeeSearchSelectEmail;
|
||||
@@ -4,7 +4,6 @@ import Markdown from "react-markdown";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { connect } from "react-redux";
|
||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component";
|
||||
import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { acceptEula } from "../../redux/user/user.actions";
|
||||
@@ -12,6 +11,7 @@ import { useTranslation } from "react-i18next";
|
||||
import day from "../../utils/day";
|
||||
|
||||
import "./eula.styles.scss";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
||||
const [formReady, setFormReady] = useState(false);
|
||||
@@ -216,7 +216,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker onChange={handleChange} onlyToday aria-label={t("eula.labels.date_accepted")} />
|
||||
<DateTimePicker isDateOnly onChange={handleChange} onlyToday aria-label={t("eula.labels.date_accepted")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import { DatePicker } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useRef } from "react";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FormDatePicker);
|
||||
|
||||
const dateFormat = "MM/DD/YYYY";
|
||||
|
||||
export function FormDatePicker({
|
||||
bodyshop,
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
onlyFuture,
|
||||
onlyToday,
|
||||
isDateOnly = true,
|
||||
...restProps
|
||||
}) {
|
||||
const ref = useRef();
|
||||
|
||||
const handleChange = (newDate) => {
|
||||
if (value !== newDate && onChange) {
|
||||
onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key.toLowerCase() === "t") {
|
||||
if (onChange) {
|
||||
onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs());
|
||||
}
|
||||
} else if (e.key.toLowerCase() === "enter") {
|
||||
if (ref.current && ref.current.blur) ref.current.blur();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBlur = (e) => {
|
||||
const v = e.target.value;
|
||||
if (!v) return;
|
||||
|
||||
const formats = [
|
||||
"MMDDYY",
|
||||
"MMDDYYYY",
|
||||
"MM/DD/YY",
|
||||
"MM/DD/YYYY",
|
||||
"M/DD/YY",
|
||||
"M/DD/YYYY",
|
||||
"MM/D/YY",
|
||||
"MM/D/YYYY",
|
||||
"M/D/YY",
|
||||
"M/D/YYYY",
|
||||
"D/MM/YY",
|
||||
"D/MM/YYYY",
|
||||
"DD/M/YY",
|
||||
"DD/M/YYYY",
|
||||
"D/M/YY",
|
||||
"D/M/YYYY"
|
||||
];
|
||||
|
||||
let _a;
|
||||
|
||||
// Iterate through formats to find the correct one
|
||||
for (let format of formats) {
|
||||
_a = dayjs(v, format);
|
||||
if (v === _a.format(format)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_a.isValid() && value && value.isValid && value.isValid()) {
|
||||
_a.set({
|
||||
hours: value.hours(),
|
||||
minutes: value.minutes(),
|
||||
seconds: value.seconds(),
|
||||
milliseconds: value.milliseconds()
|
||||
});
|
||||
}
|
||||
|
||||
if (_a.isValid() && onChange) {
|
||||
if (onlyFuture) {
|
||||
if (dayjs().subtract(1, "day").isBefore(_a)) {
|
||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||
} else {
|
||||
onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs());
|
||||
}
|
||||
} else {
|
||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div onKeyDown={handleKeyDown}>
|
||||
<DatePicker
|
||||
ref={ref}
|
||||
value={value ? dayjs(value) : null}
|
||||
onChange={handleChange}
|
||||
format={dateFormat}
|
||||
onBlur={onBlur || handleBlur}
|
||||
showToday={false}
|
||||
disabledTime
|
||||
disabledDate={(d) => {
|
||||
if (onlyToday) {
|
||||
return !dayjs().isSame(d, "day");
|
||||
} else if (onlyFuture) {
|
||||
return dayjs().subtract(1, "day").isAfter(d);
|
||||
}
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +1,105 @@
|
||||
import React, { forwardRef } from "react";
|
||||
//import DatePicker from "react-datepicker";
|
||||
//import "react-datepicker/src/stylesheets/datepicker.scss";
|
||||
import { TimePicker } from "antd";
|
||||
import { DatePicker } from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import dayjs from "../../utils/day";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
//To be used as a form element only.
|
||||
import { fuzzyMatchDate } from "./formats.js";
|
||||
|
||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps }, ref) => {
|
||||
// const handleChange = (newDate) => {
|
||||
// if (value !== newDate && onChange) {
|
||||
// onChange(newDate);
|
||||
// }
|
||||
// };
|
||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||
const [isManualInput, setIsManualInput] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newDate) => {
|
||||
if (onChange) {
|
||||
onChange(newDate || null);
|
||||
}
|
||||
setIsManualInput(false);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
(e) => {
|
||||
// Bail if this is not a manual input
|
||||
if (!isManualInput) {
|
||||
return;
|
||||
}
|
||||
// Reset manual input flag
|
||||
setIsManualInput(false);
|
||||
|
||||
const v = e?.target?.value;
|
||||
|
||||
if (!v) return;
|
||||
|
||||
let parsedDate = isDateOnly ? fuzzyMatchDate(v)?.startOf("day") : fuzzyMatchDate(v);
|
||||
|
||||
if (parsedDate && onChange) {
|
||||
onChange(parsedDate);
|
||||
}
|
||||
},
|
||||
[isManualInput, isDateOnly, onChange]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e) => {
|
||||
setIsManualInput(true);
|
||||
|
||||
if (e.key.toLowerCase() === "t" && onChange) {
|
||||
e.preventDefault();
|
||||
setIsManualInput(false);
|
||||
onChange(dayjs());
|
||||
} else if (e.key.toLowerCase() === "enter") {
|
||||
handleBlur(e);
|
||||
}
|
||||
},
|
||||
[onChange, handleBlur]
|
||||
);
|
||||
|
||||
const handleDisabledDate = useCallback(
|
||||
(current) => {
|
||||
if (onlyToday) {
|
||||
return !dayjs().isSame(current, "day");
|
||||
} else if (onlyFuture) {
|
||||
return dayjs().subtract(1, "day").isAfter(current);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[onlyToday, onlyFuture]
|
||||
);
|
||||
|
||||
return (
|
||||
<div id={id}>
|
||||
<FormDatePicker
|
||||
{...restProps}
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => dayjs().subtract(1, "day").isAfter(d)
|
||||
})}
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
onlyFuture={onlyFuture}
|
||||
isDateOnly={false}
|
||||
/>
|
||||
|
||||
<TimePicker
|
||||
<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}
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => dayjs().isAfter(d)
|
||||
})}
|
||||
onChange={onChange}
|
||||
disableSeconds={true}
|
||||
minuteStep={15}
|
||||
onBlur={onBlur}
|
||||
format="hh:mm a"
|
||||
onChange={handleChange}
|
||||
placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")}
|
||||
onBlur={onBlur || handleBlur}
|
||||
disabledDate={handleDisabledDate}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(DateTimePicker);
|
||||
DateTimePicker.propTypes = {
|
||||
value: PropTypes.any,
|
||||
onChange: PropTypes.func,
|
||||
onBlur: PropTypes.func,
|
||||
id: PropTypes.string,
|
||||
onlyFuture: PropTypes.bool,
|
||||
onlyToday: PropTypes.bool,
|
||||
isDateOnly: PropTypes.bool
|
||||
};
|
||||
|
||||
export default React.memo(DateTimePicker);
|
||||
|
||||
63
client/src/components/form-date-time-picker/formats.js
Normal file
63
client/src/components/form-date-time-picker/formats.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
const dateFormats = [
|
||||
"MMDDYYYY",
|
||||
"MMDDYY",
|
||||
"M/D/YYYY",
|
||||
"MM/D/YYYY",
|
||||
"M/DD/YYYY",
|
||||
"MM/DD/YYYY",
|
||||
"M/D/YY",
|
||||
"MM/D/YY",
|
||||
"M/DD/YY",
|
||||
"MM/DD/YY"
|
||||
];
|
||||
|
||||
const timeFormats = ["h:mm A", "h:mmA", "h A", "hA", "hh:mm A", "hh:mm:ss A"];
|
||||
|
||||
const dateTimeFormats = [
|
||||
...["M/D/YYYY", "MM/D/YYYY", "M/DD/YYYY", "MM/DD/YYYY", "M/D/YY", "MM/D/YY", "M/DD/YY", "MM/DD/YY"].flatMap(
|
||||
(dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)
|
||||
),
|
||||
|
||||
...["MMDDYYYY", "MMDDYY"].flatMap((dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)),
|
||||
|
||||
"M/D/YYYY",
|
||||
"MM/D/YYYY",
|
||||
"M/DD/YYYY",
|
||||
"MM/DD/YYYY",
|
||||
"M/D/YY",
|
||||
"MM/D/YY",
|
||||
"M/DD/YY",
|
||||
"MM/DD/YY",
|
||||
"MMDDYYYY",
|
||||
"MMDDYY"
|
||||
];
|
||||
|
||||
const sanitizeInput = (input) =>
|
||||
input
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.replace(/\s*(am|pm)\s*/i, " $1")
|
||||
.replaceAll(".", "/")
|
||||
.replaceAll("-", "/");
|
||||
|
||||
export const fuzzyMatchDate = (dateString) => {
|
||||
const sanitizedInput = sanitizeInput(dateString);
|
||||
|
||||
for (const format of dateFormats) {
|
||||
const parsedDate = dayjs(sanitizedInput, format, true);
|
||||
if (parsedDate.isValid()) {
|
||||
return parsedDate;
|
||||
}
|
||||
}
|
||||
|
||||
for (const format of dateTimeFormats) {
|
||||
const parsedDateTime = dayjs(sanitizedInput, format, true);
|
||||
if (parsedDateTime.isValid()) {
|
||||
return parsedDateTime; // Return the dayjs object
|
||||
}
|
||||
}
|
||||
|
||||
return null; // If no matching format is found
|
||||
};
|
||||
@@ -10,7 +10,8 @@ import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.compon
|
||||
|
||||
export default function GlobalSearchOs() {
|
||||
const { t } = useTranslation();
|
||||
const history = useNavigate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(false);
|
||||
|
||||
@@ -181,10 +182,13 @@ export default function GlobalSearchOs() {
|
||||
<AutoComplete
|
||||
options={data}
|
||||
onSearch={handleSearch}
|
||||
defaultActiveFirstOption
|
||||
onSelect={(val, opt) => {
|
||||
history(opt.label.props.to);
|
||||
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
|
||||
|
||||
@@ -12,8 +12,8 @@ import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.compon
|
||||
|
||||
export default function GlobalSearch() {
|
||||
const { t } = useTranslation();
|
||||
const history = useNavigate();
|
||||
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);
|
||||
@@ -21,7 +21,6 @@ export default function GlobalSearch() {
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
console.log("Handle Search");
|
||||
debouncedExecuteSearch({ variables: { search: value } });
|
||||
};
|
||||
|
||||
@@ -161,8 +160,11 @@ export default function GlobalSearch() {
|
||||
options={options}
|
||||
onSearch={handleSearch}
|
||||
defaultActiveFirstOption
|
||||
onSelect={(val, opt) => {
|
||||
history(opt.label.props.to);
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") return;
|
||||
const firstUrlForSearch = options?.[0]?.options?.[0]?.label?.props?.to;
|
||||
if (!firstUrlForSearch) return;
|
||||
navigate(firstUrlForSearch);
|
||||
}}
|
||||
>
|
||||
<Input.Search
|
||||
|
||||
@@ -13,10 +13,10 @@ import Icon, {
|
||||
FileFilled,
|
||||
HomeFilled,
|
||||
ImportOutlined,
|
||||
InfoCircleOutlined,
|
||||
LineChartOutlined,
|
||||
PaperClipOutlined,
|
||||
PhoneOutlined,
|
||||
PlusCircleOutlined,
|
||||
QuestionCircleFilled,
|
||||
ScheduleOutlined,
|
||||
SettingOutlined,
|
||||
@@ -26,11 +26,12 @@ 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 } from "react-icons/fa";
|
||||
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
||||
import { FiLogOut } from "react-icons/fi";
|
||||
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
|
||||
import { IoBusinessOutline } from "react-icons/io5";
|
||||
import { RiSurveyLine } from "react-icons/ri";
|
||||
@@ -41,8 +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 { FiLogOut } from "react-icons/fi";
|
||||
import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
|
||||
@@ -54,12 +53,43 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
setTimeTicketContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "timeTicket"
|
||||
})
|
||||
),
|
||||
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||
setReportCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "reportCenter" })),
|
||||
setReportCenterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "reportCenter"
|
||||
})
|
||||
),
|
||||
signOutStart: () => dispatch(signOutStart()),
|
||||
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" }))
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "cardPayment"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "taskUpsert"
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
function Header({
|
||||
@@ -73,7 +103,8 @@ function Header({
|
||||
setPaymentContext,
|
||||
setReportCenterContext,
|
||||
recentItems,
|
||||
setCardPaymentContext
|
||||
setCardPaymentContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const {
|
||||
treatments: { ImEXPay, DmsAp, Simple_Inventory }
|
||||
@@ -82,20 +113,22 @@ 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}`;
|
||||
console.log(`betaSwitchImex cookie deleted`);
|
||||
} else {
|
||||
console.log(`betaSwitchImex cookie does not exist`);
|
||||
}
|
||||
};
|
||||
|
||||
deleteBetaCookie();
|
||||
|
||||
const accountingChildren = [];
|
||||
|
||||
if (
|
||||
@@ -108,11 +141,13 @@ function Header({
|
||||
accountingChildren.push(
|
||||
{
|
||||
key: "bills",
|
||||
id: "header-accounting-bills",
|
||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
||||
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
|
||||
},
|
||||
{
|
||||
key: "enterbills",
|
||||
id: "header-accounting-enterbills",
|
||||
icon: <Icon component={GiPayMoney} />,
|
||||
label: t("menus.header.enterbills"),
|
||||
onClick: () => {
|
||||
@@ -132,6 +167,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "inventory",
|
||||
id: "header-accounting-inventory",
|
||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
||||
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
|
||||
}
|
||||
@@ -150,11 +186,13 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "allpayments",
|
||||
id: "header-accounting-allpayments",
|
||||
icon: <BankFilled />,
|
||||
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
||||
},
|
||||
{
|
||||
key: "enterpayments",
|
||||
id: "header-accounting-enterpayments",
|
||||
icon: <Icon component={FaCreditCard} />,
|
||||
label: t("menus.header.enterpayment"),
|
||||
onClick: () => {
|
||||
@@ -170,6 +208,7 @@ function Header({
|
||||
if (ImEXPay.treatment === "on") {
|
||||
accountingChildren.push({
|
||||
key: "entercardpayments",
|
||||
id: "header-accounting-entercardpayments",
|
||||
icon: <Icon component={FaCreditCard} />,
|
||||
label: t("menus.header.entercardpayment"),
|
||||
onClick: () => {
|
||||
@@ -194,6 +233,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "timetickets",
|
||||
id: "header-accounting-timetickets",
|
||||
icon: <FieldTimeOutlined />,
|
||||
label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
|
||||
}
|
||||
@@ -202,6 +242,7 @@ function Header({
|
||||
if (bodyshop?.md_tasks_presets?.use_approvals) {
|
||||
accountingChildren.push({
|
||||
key: "ttapprovals",
|
||||
id: "header-accounting-ttapprovals",
|
||||
icon: <FieldTimeOutlined />,
|
||||
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
||||
});
|
||||
@@ -211,6 +252,7 @@ function Header({
|
||||
key: "entertimetickets",
|
||||
icon: <Icon component={GiPlayerTime} />,
|
||||
label: t("menus.header.entertimeticket"),
|
||||
id: "header-accounting-entertimetickets",
|
||||
onClick: () => {
|
||||
setTimeTicketContext({
|
||||
actions: {},
|
||||
@@ -231,6 +273,7 @@ function Header({
|
||||
const accountingExportChildren = [
|
||||
{
|
||||
key: "receivables",
|
||||
id: "header-accounting-receivables",
|
||||
label: <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
|
||||
}
|
||||
];
|
||||
@@ -238,6 +281,7 @@ function Header({
|
||||
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || DmsAp.treatment === "on") {
|
||||
accountingExportChildren.push({
|
||||
key: "payables",
|
||||
id: "header-accounting-payables",
|
||||
label: <Link to="/manage/accounting/payables">{t("menus.header.accounting-payables")}</Link>
|
||||
});
|
||||
}
|
||||
@@ -245,6 +289,7 @@ function Header({
|
||||
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber))) {
|
||||
accountingExportChildren.push({
|
||||
key: "payments",
|
||||
id: "header-accounting-payments",
|
||||
label: <Link to="/manage/accounting/payments">{t("menus.header.accounting-payments")}</Link>
|
||||
});
|
||||
}
|
||||
@@ -255,6 +300,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "exportlogs",
|
||||
id: "header-accounting-exportlogs",
|
||||
label: <Link to="/manage/accounting/exportlogs">{t("menus.header.export-logs")}</Link>
|
||||
}
|
||||
);
|
||||
@@ -268,6 +314,7 @@ function Header({
|
||||
) {
|
||||
accountingChildren.push({
|
||||
key: "accountingexport",
|
||||
id: "header-accounting-export",
|
||||
icon: <ExportOutlined />,
|
||||
label: t("menus.header.export"),
|
||||
children: accountingExportChildren
|
||||
@@ -278,10 +325,12 @@ function Header({
|
||||
{
|
||||
key: "home",
|
||||
icon: <HomeFilled />,
|
||||
id: "header-home",
|
||||
label: <Link to="/manage/">{t("menus.header.home")}</Link>
|
||||
},
|
||||
{
|
||||
key: "schedule",
|
||||
id: "header-schedule",
|
||||
icon: <Icon component={FaCalendarAlt} />,
|
||||
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
|
||||
},
|
||||
@@ -293,16 +342,19 @@ function Header({
|
||||
children: [
|
||||
{
|
||||
key: "activejobs",
|
||||
id: "header-active-jobs",
|
||||
icon: <FileFilled />,
|
||||
label: <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
||||
},
|
||||
{
|
||||
key: "readyjobs",
|
||||
id: "header-ready-jobs",
|
||||
icon: <CheckCircleOutlined />,
|
||||
label: <Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
|
||||
},
|
||||
{
|
||||
key: "parts-queue",
|
||||
id: "header-parts-queue",
|
||||
icon: <ToolFilled />,
|
||||
label: <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
|
||||
},
|
||||
@@ -314,6 +366,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "newjob",
|
||||
id: "header-new-job",
|
||||
icon: <FileAddOutlined />,
|
||||
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
|
||||
},
|
||||
@@ -322,6 +375,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "alljobs",
|
||||
id: "header-all-jobs",
|
||||
icon: <UnorderedListOutlined />,
|
||||
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
|
||||
},
|
||||
@@ -330,6 +384,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "productionlist",
|
||||
id: "header-production-list",
|
||||
icon: <ScheduleOutlined />,
|
||||
label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
|
||||
},
|
||||
@@ -341,6 +396,7 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "productionboard",
|
||||
id: "header-production-board",
|
||||
icon: <Icon component={BsKanban} />,
|
||||
label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
|
||||
}
|
||||
@@ -358,6 +414,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "scoreboard",
|
||||
id: "header-scoreboard",
|
||||
icon: <LineChartOutlined />,
|
||||
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
|
||||
}
|
||||
@@ -368,15 +425,18 @@ function Header({
|
||||
{
|
||||
key: "customers",
|
||||
icon: <UserOutlined />,
|
||||
id: "header-customers",
|
||||
label: t("menus.header.customers"),
|
||||
children: [
|
||||
{
|
||||
key: "owners",
|
||||
id: "header-owners",
|
||||
icon: <TeamOutlined />,
|
||||
label: <Link to="/manage/owners">{t("menus.header.owners")}</Link>
|
||||
},
|
||||
{
|
||||
key: "vehicles",
|
||||
id: "header-vehicles",
|
||||
icon: <CarFilled />,
|
||||
label: <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
|
||||
}
|
||||
@@ -390,21 +450,25 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "ccs",
|
||||
id: "header-css",
|
||||
icon: <CarFilled />,
|
||||
label: t("menus.header.courtesycars"),
|
||||
children: [
|
||||
{
|
||||
key: "courtesycarsall",
|
||||
id: "header-courtesycars-all",
|
||||
icon: <CarFilled />,
|
||||
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
|
||||
},
|
||||
{
|
||||
key: "contracts",
|
||||
id: "header-contracts",
|
||||
icon: <FileFilled />,
|
||||
label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
|
||||
},
|
||||
{
|
||||
key: "newcontract",
|
||||
id: "header-newcontract",
|
||||
icon: <FileAddFilled />,
|
||||
label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
|
||||
}
|
||||
@@ -417,6 +481,7 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "accounting",
|
||||
id: "header-accounting",
|
||||
icon: <DollarCircleFilled />,
|
||||
label: t("menus.header.accounting"),
|
||||
children: accountingChildren
|
||||
@@ -425,6 +490,7 @@ function Header({
|
||||
: []),
|
||||
{
|
||||
key: "phonebook",
|
||||
id: "header-phonebook",
|
||||
icon: <PhoneOutlined />,
|
||||
label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
|
||||
},
|
||||
@@ -436,28 +502,62 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "temporarydocs",
|
||||
id: "header-temporarydocs",
|
||||
icon: <PaperClipOutlined />,
|
||||
label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "tasks",
|
||||
id: "tasks",
|
||||
icon: <FaTasks />,
|
||||
label: t("menus.header.tasks"),
|
||||
children: [
|
||||
{
|
||||
key: "createTask",
|
||||
icon: <PlusCircleOutlined />,
|
||||
label: t("menus.header.create_task"),
|
||||
onClick: () => {
|
||||
setTaskUpsertContext({
|
||||
actions: {},
|
||||
context: {}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "mytasks",
|
||||
icon: <FaTasks />,
|
||||
label: <Link to="/manage/tasks/mytasks">{t("menus.header.my_tasks")}</Link>
|
||||
},
|
||||
{
|
||||
key: "all_tasks",
|
||||
icon: <FaTasks />,
|
||||
label: <Link to="/manage/tasks/alltasks">{t("menus.header.all_tasks")}</Link>
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "shopsubmenu",
|
||||
id: "header-shopsubmenu",
|
||||
icon: <SettingOutlined />,
|
||||
label: t("menus.header.shop"),
|
||||
children: [
|
||||
{
|
||||
key: "shop",
|
||||
id: "header-shop",
|
||||
icon: <Icon component={GiSettingsKnobs} />,
|
||||
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
|
||||
},
|
||||
{
|
||||
key: "dashboard",
|
||||
id: "header-dashboard",
|
||||
icon: <DashboardFilled />,
|
||||
label: <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
|
||||
},
|
||||
{
|
||||
key: "reportcenter",
|
||||
id: "header-reportcenter",
|
||||
icon: <BarChartOutlined />,
|
||||
label: t("menus.header.reportcenter"),
|
||||
onClick: () => {
|
||||
@@ -469,6 +569,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "shop-vendors",
|
||||
id: "header-shop-vendors",
|
||||
icon: <Icon component={IoBusinessOutline} />,
|
||||
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
|
||||
},
|
||||
@@ -480,6 +581,7 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "shop-csi",
|
||||
id: "header-shop-csi",
|
||||
icon: <Icon component={RiSurveyLine} />,
|
||||
label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
|
||||
}
|
||||
@@ -493,6 +595,7 @@ function Header({
|
||||
children: [
|
||||
{
|
||||
key: "signout",
|
||||
id: "header-signout",
|
||||
icon: <Icon component={FiLogOut} />,
|
||||
danger: true,
|
||||
label: t("user.actions.signout"),
|
||||
@@ -500,6 +603,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "help",
|
||||
id: "header-help",
|
||||
icon: <Icon component={QuestionCircleFilled} />,
|
||||
label: t("menus.header.help"),
|
||||
onClick: () => {
|
||||
@@ -514,14 +618,22 @@ function Header({
|
||||
);
|
||||
}
|
||||
},
|
||||
// {
|
||||
// key: 'rescue',
|
||||
// icon: <Icon component={CarFilled}/>,
|
||||
// label: t("menus.header.rescueme"),
|
||||
// onClick: () => {
|
||||
// window.open("https://imexrescue.com/", "_blank");
|
||||
// }
|
||||
// },
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: false,
|
||||
promanager: false
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "rescue",
|
||||
icon: <Icon component={CarFilled} />,
|
||||
label: t("menus.header.rescueme"),
|
||||
onClick: () => {
|
||||
window.open("https://imexrescue.com/", "_blank");
|
||||
}
|
||||
}
|
||||
]
|
||||
: []),
|
||||
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
@@ -531,6 +643,7 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "shiftclock",
|
||||
id: "header-shiftclock",
|
||||
icon: <Icon component={GiPlayerTime} />,
|
||||
label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
|
||||
}
|
||||
@@ -538,6 +651,7 @@ function Header({
|
||||
: []),
|
||||
{
|
||||
key: "profile",
|
||||
id: "header-profile",
|
||||
icon: <UserOutlined />,
|
||||
label: <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
|
||||
}
|
||||
@@ -573,6 +687,7 @@ function Header({
|
||||
{
|
||||
key: "recent",
|
||||
icon: <ClockCircleFilled />,
|
||||
id: "header-recent",
|
||||
children: recentItems.map((i, idx) => ({
|
||||
key: idx,
|
||||
label: <Link to={i.url}>{i.label}</Link>
|
||||
@@ -580,30 +695,6 @@ function Header({
|
||||
}
|
||||
];
|
||||
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
args: [],
|
||||
imex: () => {
|
||||
menuItems.push({
|
||||
key: "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
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Alert, Card, Col, Row, Space, Statistic, Tooltip, Typography } from 'antd';
|
||||
import Dinero from 'dinero.js';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import InstanceRenderManager from '../../utils/instanceRenderMgr';
|
||||
import AlertComponent from '../alert/alert.component';
|
||||
import LoadingSkeleton from '../loading-skeleton/loading-skeleton.component';
|
||||
import './job-bills-total.styles.scss';
|
||||
import { Alert, Card, Col, Row, Space, Statistic, Tooltip, Typography } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import "./job-bills-total.styles.scss";
|
||||
|
||||
export default function JobBillsTotalComponent({
|
||||
loading,
|
||||
@@ -13,16 +13,16 @@ export default function JobBillsTotalComponent({
|
||||
partsOrders,
|
||||
jobTotals,
|
||||
showWarning,
|
||||
warningCallback,
|
||||
warningCallback
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (loading) return <LoadingSkeleton />;
|
||||
if (!!!jobTotals) {
|
||||
if (showWarning && warningCallback && typeof warningCallback === 'function') {
|
||||
warningCallback({ key: 'bills', warning: t('jobs.errors.nofinancial') });
|
||||
if (showWarning && warningCallback && typeof warningCallback === "function") {
|
||||
warningCallback({ key: "bills", warning: t("jobs.errors.nofinancial") });
|
||||
}
|
||||
return <AlertComponent type="error" message={t('jobs.errors.nofinancial')} />;
|
||||
return <AlertComponent type="error" message={t("jobs.errors.nofinancial")} />;
|
||||
}
|
||||
|
||||
const totals = jobTotals;
|
||||
@@ -109,64 +109,60 @@ export default function JobBillsTotalComponent({
|
||||
const discrepWithCms = discrepWithLbrAdj.add(totalReturns);
|
||||
const calculatedCreditsNotReceived = totalReturns.subtract(billCms); //billCms is tracked as a negative number.
|
||||
|
||||
if (showWarning && warningCallback && typeof warningCallback === 'function') {
|
||||
if (
|
||||
discrepWithCms.getAmount() !== 0 ||
|
||||
discrepWithLbrAdj.getAmount() !== 0 ||
|
||||
discrepancy.getAmount() !== 0
|
||||
) {
|
||||
if (showWarning && warningCallback && typeof warningCallback === "function") {
|
||||
if (discrepWithCms.getAmount() !== 0) {
|
||||
warningCallback({
|
||||
key: 'bills',
|
||||
warning: t('jobs.labels.outstanding_reconciliation_discrep'),
|
||||
key: "bills",
|
||||
warning: t("jobs.labels.outstanding_reconciliation_discrep")
|
||||
});
|
||||
}
|
||||
if (calculatedCreditsNotReceived.getAmount() > 0) {
|
||||
warningCallback({ key: 'cm', warning: t('jobs.labels.outstanding_credit_memos') });
|
||||
warningCallback({ key: "cm", warning: t("jobs.labels.outstanding_credit_memos") });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col md={24} lg={18}>
|
||||
<Card title={t('jobs.labels.jobtotals')} style={{ height: '100%' }}>
|
||||
<Card title={t("jobs.labels.jobtotals")} style={{ height: "100%" }}>
|
||||
<Space wrap size="large">
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.partstotal'),
|
||||
__html: t("jobs.labels.plitooltips.partstotal")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic title={t('jobs.labels.rosaletotal')} value={totalPartsSublet.toFormat()} />
|
||||
<Statistic title={t("jobs.labels.rosaletotal")} value={totalPartsSublet.toFormat()} />
|
||||
</Tooltip>
|
||||
<Typography.Title>-</Typography.Title>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.billtotal'),
|
||||
__html: t("jobs.labels.plitooltips.billtotal")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic title={t('bills.labels.retailtotal')} value={billTotals.toFormat()} />
|
||||
<Statistic title={t("bills.labels.retailtotal")} value={billTotals.toFormat()} />
|
||||
</Tooltip>
|
||||
<Typography.Title>=</Typography.Title>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.discrep1'),
|
||||
__html: t("jobs.labels.plitooltips.discrep1")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic
|
||||
title={t('bills.labels.discrepancy')}
|
||||
title={t("bills.labels.discrepancy")}
|
||||
valueStyle={{
|
||||
color: discrepancy.getAmount() === 0 ? 'green' : 'red',
|
||||
color: discrepancy.getAmount() === 0 ? "green" : "red"
|
||||
}}
|
||||
value={discrepancy.toFormat()}
|
||||
/>
|
||||
@@ -176,27 +172,27 @@ export default function JobBillsTotalComponent({
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.laboradj'),
|
||||
__html: t("jobs.labels.plitooltips.laboradj")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic title={t('bills.labels.dedfromlbr')} value={lbrAdjustments.toFormat()} />
|
||||
<Statistic title={t("bills.labels.dedfromlbr")} value={lbrAdjustments.toFormat()} />
|
||||
</Tooltip>
|
||||
<Typography.Title>=</Typography.Title>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.discrep2'),
|
||||
__html: t("jobs.labels.plitooltips.discrep2")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic
|
||||
title={t('bills.labels.discrepancy')}
|
||||
title={t("bills.labels.discrepancy")}
|
||||
valueStyle={{
|
||||
color: discrepWithLbrAdj.getAmount() === 0 ? 'green' : 'red',
|
||||
color: discrepWithLbrAdj.getAmount() === 0 ? "green" : "red"
|
||||
}}
|
||||
value={discrepWithLbrAdj.toFormat()}
|
||||
/>
|
||||
@@ -206,27 +202,27 @@ export default function JobBillsTotalComponent({
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.creditmemos'),
|
||||
__html: t("jobs.labels.plitooltips.creditmemos")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic title={t('bills.labels.totalreturns')} value={totalReturns.toFormat()} />
|
||||
<Statistic title={t("bills.labels.totalreturns")} value={totalReturns.toFormat()} />
|
||||
</Tooltip>
|
||||
<Typography.Title>=</Typography.Title>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.discrep3'),
|
||||
__html: t("jobs.labels.plitooltips.discrep3")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic
|
||||
title={t('bills.labels.discrepancy')}
|
||||
title={t("bills.labels.discrepancy")}
|
||||
valueStyle={{
|
||||
color: discrepWithCms.getAmount() === 0 ? 'green' : 'red',
|
||||
color: discrepWithCms.getAmount() === 0 ? "green" : "red"
|
||||
}}
|
||||
value={discrepWithCms.toFormat()}
|
||||
/>
|
||||
@@ -238,40 +234,40 @@ export default function JobBillsTotalComponent({
|
||||
discrepWithLbrAdj.getAmount() !== 0 ||
|
||||
discrepancy.getAmount() !== 0) && (
|
||||
<Alert
|
||||
style={{ margin: '8px 0px' }}
|
||||
style={{ margin: "8px 0px" }}
|
||||
type="warning"
|
||||
message={t('jobs.labels.outstanding_reconciliation_discrep')}
|
||||
message={t("jobs.labels.outstanding_reconciliation_discrep")}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
<Col md={24} lg={6}>
|
||||
<Card title={t('jobs.labels.returntotals')} style={{ height: '100%' }}>
|
||||
<Card title={t("jobs.labels.returntotals")} style={{ height: "100%" }}>
|
||||
<Space wrap>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.totalreturns'),
|
||||
__html: t("jobs.labels.plitooltips.totalreturns")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic title={t('bills.labels.totalreturns')} value={totalReturns.toFormat()} />
|
||||
<Statistic title={t("bills.labels.totalreturns")} value={totalReturns.toFormat()} />
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.calculatedcreditsnotreceived'),
|
||||
__html: t("jobs.labels.plitooltips.calculatedcreditsnotreceived")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic
|
||||
title={t('bills.labels.calculatedcreditsnotreceived')}
|
||||
title={t("bills.labels.calculatedcreditsnotreceived")}
|
||||
valueStyle={{
|
||||
color: calculatedCreditsNotReceived.getAmount() <= 0 ? 'green' : 'red',
|
||||
color: calculatedCreditsNotReceived.getAmount() <= 0 ? "green" : "red"
|
||||
}}
|
||||
value={
|
||||
calculatedCreditsNotReceived.getAmount() >= 0
|
||||
@@ -284,15 +280,15 @@ export default function JobBillsTotalComponent({
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.creditsnotreceived'),
|
||||
__html: t("jobs.labels.plitooltips.creditsnotreceived")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic
|
||||
title={t('bills.labels.creditsnotreceived')}
|
||||
title={t("bills.labels.creditsnotreceived")}
|
||||
valueStyle={{
|
||||
color: totalReturnsMarkedNotReceived.getAmount() <= 0 ? 'green' : 'red',
|
||||
color: totalReturnsMarkedNotReceived.getAmount() <= 0 ? "green" : "red"
|
||||
}}
|
||||
value={
|
||||
totalReturnsMarkedNotReceived.getAmount() >= 0
|
||||
@@ -303,11 +299,7 @@ export default function JobBillsTotalComponent({
|
||||
</Tooltip>
|
||||
</Space>
|
||||
{showWarning && calculatedCreditsNotReceived.getAmount() > 0 && (
|
||||
<Alert
|
||||
style={{ margin: '8px 0px' }}
|
||||
type="warning"
|
||||
message={t('jobs.labels.outstanding_credit_memos')}
|
||||
/>
|
||||
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_credit_memos")} />
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Form, Input, notification, Switch } from "antd";
|
||||
import dayjs from "../../../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -14,6 +13,7 @@ import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
|
||||
import { insertAuditTrail } from "../../../../redux/application/application.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
|
||||
import dayjs from "../../../../utils/day";
|
||||
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
|
||||
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
|
||||
|
||||
@@ -275,7 +275,19 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
|
||||
>
|
||||
<DateTimePicker disabled={readOnly} />
|
||||
</Form.Item>
|
||||
<Form.Item name="actual_delivery" label={t("jobs.fields.actual_delivery")} disabled={readOnly}>
|
||||
<Form.Item
|
||||
name="actual_delivery"
|
||||
label={t("jobs.fields.actual_delivery")}
|
||||
rules={[
|
||||
{
|
||||
required: bodyshop.deliverchecklist.actual_delivery
|
||||
? bodyshop.deliverchecklist.actual_delivery
|
||||
: false
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
disabled={readOnly}
|
||||
>
|
||||
<DateTimePicker disabled={readOnly} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useSplitTreatments } from '@splitsoftware/splitio-react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { GET_LINE_TICKET_BY_PK } from '../../graphql/jobs-lines.queries';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import AlertComponent from '../alert/alert.component';
|
||||
import LaborAllocationsTableComponent from '../labor-allocations-table/labor-allocations-table.component';
|
||||
import PayrollLaborAllocationsTable from '../labor-allocations-table/labor-allocations-table.payroll.component';
|
||||
import LoadingSkeleton from '../loading-skeleton/loading-skeleton.component';
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LaborAllocationsTableComponent from "../labor-allocations-table/labor-allocations-table.component";
|
||||
import PayrollLaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.payroll.component";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -24,21 +24,21 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRoGuardLabor
|
||||
export function JobCloseRoGuardLabor({ job, jobRO, bodyshop, form, warningCallback }) {
|
||||
const { loading, error, data, refetch } = useQuery(GET_LINE_TICKET_BY_PK, {
|
||||
variables: { id: job.id },
|
||||
fetchPolicy: 'network-only',
|
||||
nextFetchPolicy: 'network-only',
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
const {
|
||||
treatments: { Enhanced_Payroll },
|
||||
treatments: { Enhanced_Payroll }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ['Enhanced_Payroll'],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
if (loading) return <LoadingSkeleton />;
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return Enhanced_Payroll.treatment === 'on' ? (
|
||||
return Enhanced_Payroll.treatment === "on" ? (
|
||||
<PayrollLaborAllocationsTable
|
||||
jobId={job.id}
|
||||
timetickets={data ? data.timetickets : []}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { Alert, Card } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import { useMemo } from 'react';
|
||||
import Dinero from 'dinero.js';
|
||||
import DataLabel from '../data-label/data-label.component';
|
||||
import { Alert, Card } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useMemo } from "react";
|
||||
import Dinero from "dinero.js";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -41,25 +41,21 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
|
||||
|
||||
useEffect(() => {
|
||||
if (balance.getAmount() !== 0) {
|
||||
warningCallback({ key: 'ar', warning: t('jobs.labels.outstanding_ar') });
|
||||
warningCallback({ key: "ar", warning: t("jobs.labels.outstanding_ar") });
|
||||
}
|
||||
}, [balance, t, warningCallback]);
|
||||
|
||||
return (
|
||||
<Card title={t('jobs.labels.accountsreceivable')} style={{ height: '100%' }}>
|
||||
<DataLabel label={t('payments.labels.totalpayments')}>{total.toFormat()}</DataLabel>
|
||||
<Card title={t("jobs.labels.accountsreceivable")} style={{ height: "100%" }}>
|
||||
<DataLabel label={t("payments.labels.totalpayments")}>{total.toFormat()}</DataLabel>
|
||||
<DataLabel
|
||||
valueStyle={{ color: balance.getAmount() !== 0 ? 'red' : 'green' }}
|
||||
label={t('payments.labels.balance')}
|
||||
valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }}
|
||||
label={t("payments.labels.balance")}
|
||||
>
|
||||
{balance.toFormat()}
|
||||
</DataLabel>
|
||||
{balance.getAmount() !== 0 && (
|
||||
<Alert
|
||||
style={{ margin: '8px 0px' }}
|
||||
type="warning"
|
||||
message={t('jobs.labels.outstanding_ar')}
|
||||
/>
|
||||
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_ar")} />
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { QUERY_BILLS_BY_JOBID } from '../../graphql/bills.queries';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import AlertComponent from '../alert/alert.component';
|
||||
import JobBillsTotalComponent from '../job-bills-total/job-bills-total.component';
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_PARTS_BILLS_BY_JOBID } from "../../graphql/bills.queries";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobBillsTotalComponent from "../job-bills-total/job-bills-total.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -19,10 +19,10 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRoGuardBills);
|
||||
|
||||
export function JobCloseRoGuardBills({ job, jobRO, bodyshop, form, warningCallback }) {
|
||||
const { loading, error, data } = useQuery(QUERY_BILLS_BY_JOBID, {
|
||||
const { loading, error, data } = useQuery(QUERY_PARTS_BILLS_BY_JOBID, {
|
||||
variables: { jobid: job.id },
|
||||
fetchPolicy: 'network-only',
|
||||
nextFetchPolicy: 'network-only',
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { LockOutlined } from "@ant-design/icons";
|
||||
import { Badge, Card, Col, Collapse, Form, Input, Row, Space, Tooltip } from "antd";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -12,9 +12,8 @@ import JobCloseRoGuardBills from "./job-close-ro-guard.bills";
|
||||
import JobCloseRoGuardPpd from "./job-close-ro-guard.ppd";
|
||||
import JobCloseRoGuardProfit from "./job-close-ro-guard.profit";
|
||||
import "./job-close-ro-guard.styles.scss";
|
||||
import JobCloseRoGuardSublet from "./job-close-ro-guard.sublet";
|
||||
import JobCloseRoGuardTtLifecycle from "./job-close-ro-guard.tt-lifecycle";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import JobCloseRoGuardTtLifecycle from "./job-close-ro-guard.tt-lifecycle";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -76,20 +75,21 @@ export function JobCloseRoGuardContainer({ job, jobRO, bodyshop, form }) {
|
||||
<Col className="ro-guard-col-multiple" md={24} lg={6}>
|
||||
<Row gutter={[32, 32]} style={{ height: "100%" }}>
|
||||
<Col span={24}>
|
||||
<JobCloseRoGuardProfit
|
||||
job={job}
|
||||
form={form} //warningCallback={warningCallback}
|
||||
/>
|
||||
<JobCloseRoGuardProfit job={job} form={form} warningCallback={warningCallback} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<JobCloseRoGuardAr job={job} form={form} warningCallback={warningCallback} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col className="ro-guard-col-50" md={24} lg={8}>
|
||||
<JobCloseRoGuardSublet job={job} warningCallback={warningCallback} />
|
||||
{InstanceRenderManager({ rome: <JobCloseRoGuardPpd job={job} warningCallback={warningCallback} /> })}
|
||||
</Col>
|
||||
{InstanceRenderManager({
|
||||
rome: (
|
||||
<Col md={24} lg={8}>
|
||||
{/* <JobCloseRoGuardSublet job={job} warningCallback={warningCallback} /> */}
|
||||
<JobCloseRoGuardPpd job={job} warningCallback={warningCallback} />
|
||||
</Col>
|
||||
)
|
||||
})}
|
||||
<Col className="ro-guard-col" md={24} lg={10}>
|
||||
<JobCloseRoGuardLabor job={job} form={form} warningCallback={warningCallback} />
|
||||
</Col>
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { LockOutlined } from '@ant-design/icons';
|
||||
import { Card, Form, Input } from 'antd';
|
||||
import axios from 'axios';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import JobCostingStatistics from '../job-costing-statistics/job-costing-statistics.component';
|
||||
import LoadingSkeleton from '../loading-skeleton/loading-skeleton.component';
|
||||
import { Alert, Card } from "antd";
|
||||
import axios from "axios";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import JobCostingStatistics from "../job-costing-statistics/job-costing-statistics.component";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -31,7 +30,7 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
|
||||
try {
|
||||
if (job.id) {
|
||||
setLoading(true);
|
||||
const { data } = await axios.post('/job/costing', { jobid: job.id });
|
||||
const { data } = await axios.post("/job/costing", { jobid: job.id });
|
||||
setCostingData(data);
|
||||
}
|
||||
} catch (error) {}
|
||||
@@ -45,16 +44,19 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
|
||||
parseFloat(costingData?.summaryData.gppercent) < bodyshop?.md_ro_guard?.totalgppercent_minimum;
|
||||
|
||||
useEffect(() => {
|
||||
if (enforceProfitPassword && typeof warningCallback === 'function') {
|
||||
warningCallback({ key: 'profit', warning: t('jobs.labels.profitbypassrequired') });
|
||||
if (enforceProfitPassword && typeof warningCallback === "function") {
|
||||
warningCallback({ key: "profit", warning: t("jobs.labels.profitbypassrequired") });
|
||||
}
|
||||
}, [enforceProfitPassword, t, warningCallback]);
|
||||
|
||||
if (loading || !costingData) return <LoadingSkeleton />;
|
||||
|
||||
return (
|
||||
<Card title={t('jobs.labels.profits')} style={{ height: '100%' }}>
|
||||
<Card title={t("jobs.labels.profits")} style={{ height: "100%" }}>
|
||||
<JobCostingStatistics summaryData={costingData?.summaryData} onlyGP />
|
||||
{enforceProfitPassword && (
|
||||
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.profitbypassrequired")} />
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { Alert, Card, Table } from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import CurrencyFormatter from '../../utils/CurrencyFormatter';
|
||||
import { alphaSort } from '../../utils/sorters';
|
||||
import { Alert, Card, Table } from "antd";
|
||||
import { t } from "i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -20,69 +20,56 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRGuardSublet
|
||||
|
||||
export function JobCloseRGuardSublet({ job, jobRO, bodyshop, form, warningCallback }) {
|
||||
const subletsNotDone = job?.joblines.filter(
|
||||
(j) =>
|
||||
(j.part_type === 'PAS' || j.part_type === 'PASL') &&
|
||||
(!j.sublet_completed || !j.sublet_ignored)
|
||||
(j) => (j.part_type === "PAS" || j.part_type === "PASL") && (!j.sublet_completed || !j.sublet_ignored)
|
||||
);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t('joblines.fields.line_desc'),
|
||||
dataIndex: 'line_desc',
|
||||
fixed: 'left',
|
||||
key: 'line_desc',
|
||||
title: t("joblines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
fixed: "left",
|
||||
key: "line_desc",
|
||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||
onCell: (record) => ({
|
||||
className: record.manual_line && 'job-line-manual',
|
||||
className: record.manual_line && "job-line-manual",
|
||||
style: {
|
||||
...(record.critical ? { boxShadow: ' -.5em 0 0 #FFC107' } : {}),
|
||||
},
|
||||
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {})
|
||||
}
|
||||
}),
|
||||
ellipsis: true,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t('joblines.fields.act_price'),
|
||||
dataIndex: 'act_price',
|
||||
key: 'act_price',
|
||||
title: t("joblines.fields.act_price"),
|
||||
dataIndex: "act_price",
|
||||
key: "act_price",
|
||||
sorter: (a, b) => a.act_price - b.act_price,
|
||||
|
||||
ellipsis: true,
|
||||
render: (text, record) => <CurrencyFormatter>{record.act_price}</CurrencyFormatter>,
|
||||
render: (text, record) => <CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||
},
|
||||
{
|
||||
title: t('joblines.fields.part_qty'),
|
||||
dataIndex: 'part_qty',
|
||||
key: 'part_qty',
|
||||
title: t("joblines.fields.part_qty"),
|
||||
dataIndex: "part_qty",
|
||||
key: "part_qty"
|
||||
},
|
||||
{
|
||||
title: t('joblines.fields.notes'),
|
||||
dataIndex: 'notes',
|
||||
key: 'notes',
|
||||
},
|
||||
title: t("joblines.fields.notes"),
|
||||
dataIndex: "notes",
|
||||
key: "notes"
|
||||
}
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (subletsNotDone.length > 0) {
|
||||
warningCallback({ key: 'sublet', warning: t('jobs.labels.outstanding_sublets') });
|
||||
warningCallback({ key: "sublet", warning: t("jobs.labels.outstanding_sublets") });
|
||||
}
|
||||
}, [subletsNotDone.length, warningCallback]);
|
||||
|
||||
return (
|
||||
<Card title={t('jobs.labels.subletsnotcompleted')}>
|
||||
<Table
|
||||
dataSource={subletsNotDone}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
bordered
|
||||
size="small"
|
||||
/>
|
||||
<Card title={t("jobs.labels.subletsnotcompleted")}>
|
||||
<Table dataSource={subletsNotDone} columns={columns} pagination={false} rowKey="id" bordered size="small" />
|
||||
{subletsNotDone.length > 0 && (
|
||||
<Alert
|
||||
style={{ margin: '8px 0px' }}
|
||||
type="warning"
|
||||
message={t('jobs.labels.outstanding_sublets')}
|
||||
/>
|
||||
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_sublets")} />
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import JobLifecycleComponent from '../job-lifecycle/job-lifecycle.component';
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import JobLifecycleComponent from "../job-lifecycle/job-lifecycle.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
import Car from "../job-damage-visual/job-damage-visual.component";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsDamageComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
const { area_of_damage } = data;
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}>
|
||||
{area_of_damage ? <Car dmg1={area_of_damage.impact1} dmg2={area_of_damage.impact2} /> : t("jobs.errors.nodamage")}
|
||||
{area_of_damage ? (
|
||||
<Car
|
||||
dmg1={area_of_damage.impact1 && area_of_damage.impact1.padStart(2, "0")}
|
||||
dmg2={area_of_damage.impact2 && area_of_damage.impact2.padStart(2, "0")}
|
||||
/>
|
||||
) : (
|
||||
t("jobs.errors.nodamage")
|
||||
)}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,16 @@ export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
|
||||
const { t } = useTranslation();
|
||||
const { joblines_status } = data;
|
||||
|
||||
const filteredJobLines = data.joblines.filter(
|
||||
(j) =>
|
||||
j.part_type !== null &&
|
||||
j.part_type !== "PAE" &&
|
||||
j.part_type !== "PAS" &&
|
||||
j.part_type !== "PASL" &&
|
||||
j.part_qty !== 0 &&
|
||||
j.act_price !== 0
|
||||
);
|
||||
//TODO: Correct jobline_statuses view by including the part_qty !== 0 and act_price !== 0
|
||||
const columns = [
|
||||
{
|
||||
title: t("joblines.fields.line_desc"),
|
||||
@@ -95,7 +105,7 @@ export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
|
||||
<div>
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
|
||||
<PartsStatusPie joblines_status={joblines_status} />
|
||||
<Table key="id" columns={columns} dataSource={data ? data.joblines : []} />
|
||||
<Table key="id" columns={columns} dataSource={filteredJobLines ? filteredJobLines : []} />
|
||||
</CardTemplate>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd";
|
||||
import { Col, Row, Skeleton, Space, Timeline, Typography } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
|
||||
import { QUERY_JOBLINE_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors.js";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import BillDetailEditcontainer from "../bill-detail-edit/bill-detail-edit.container.jsx";
|
||||
import FeatureWrapper from "../feature-wrapper/feature-wrapper.component.jsx";
|
||||
import TaskListContainer from "../task-list/task-list.container.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
|
||||
|
||||
export function JobLinesExpander({ jobline, jobid, bodyshop }) {
|
||||
export function JobLinesExpander({ jobline, jobid, bodyshop, technician }) {
|
||||
const { t } = useTranslation();
|
||||
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
|
||||
fetchPolicy: "network-only",
|
||||
@@ -43,13 +48,31 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
|
||||
? data.parts_order_lines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Space split={<Divider type="vertical" />} wrap>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
|
||||
{line.parts_order.order_number}
|
||||
</Link>
|
||||
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
||||
{line.parts_order.vendor.name}
|
||||
</Space>
|
||||
<Row wrap>
|
||||
<Col span={4}>
|
||||
{!technician ? (
|
||||
<>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
|
||||
{line.parts_order.order_number}
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
`${line.parts_order.order_number}`
|
||||
)}
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
||||
</Col>
|
||||
<Col span={4}>{line.parts_order.vendor.name}</Col>
|
||||
{line.backordered_eta ? (
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("parts_orders.fields.backordered_eta")}: `}
|
||||
<DateFormatter>{line.backordered_eta}</DateFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
) : null}
|
||||
</Row>
|
||||
)
|
||||
}))
|
||||
: [
|
||||
@@ -61,48 +84,6 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
|
||||
}
|
||||
/>{" "}
|
||||
</Col>
|
||||
<Col md={24} lg={8}>
|
||||
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
|
||||
<Timeline
|
||||
items={
|
||||
data.billlines.length > 0
|
||||
? data.billlines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Row wrap>
|
||||
<Col span={4}>
|
||||
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
|
||||
{line.bill.invoice_number}
|
||||
</Link>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_price")}: `}
|
||||
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_cost")}: `}
|
||||
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<DateFormatter>{line.bill.date}</DateFormatter>
|
||||
</Col>
|
||||
<Col span={4}> {line.bill.vendor.name}</Col>
|
||||
</Row>
|
||||
)
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: "no-orders",
|
||||
children: t("bills.labels.nobilllines")
|
||||
}
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={24} lg={8}>
|
||||
<Typography.Title level={4}>{t("parts_dispatch.labels.parts_dispatch")}</Typography.Title>
|
||||
<Timeline
|
||||
@@ -111,23 +92,91 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
|
||||
? data.parts_dispatch_lines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Space split={<Divider type="vertical" />} wrap>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
|
||||
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
|
||||
<Space>
|
||||
{t("parts_dispatch_lines.fields.accepted_at")}
|
||||
<DateFormatter>{line.accepted_at}</DateFormatter>
|
||||
</Space>
|
||||
</Space>
|
||||
<Row>
|
||||
<Col span={8}>{line.parts_dispatch.number}</Col>
|
||||
<Col span={8}>
|
||||
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
{line.accepted_at ? (
|
||||
<Space>
|
||||
{t("parts_dispatch_lines.fields.accepted_at")}
|
||||
<DateFormatter>{line.accepted_at}</DateFormatter>
|
||||
</Space>
|
||||
) : null}
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}))
|
||||
: {
|
||||
key: "dispatch-lines",
|
||||
children: t("parts_orders.labels.notyetordered")
|
||||
}
|
||||
: [
|
||||
{
|
||||
key: "dispatch-lines",
|
||||
children: t("parts_dispatch.labels.notyetdispatched")
|
||||
}
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<FeatureWrapper featureName="bills" noauth={() => null}>
|
||||
<Col md={24} lg={8}>
|
||||
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
|
||||
<BillDetailEditcontainer />
|
||||
<Timeline
|
||||
items={
|
||||
data.billlines.length > 0
|
||||
? data.billlines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Row wrap>
|
||||
<Col span={4}>
|
||||
{!technician ? (
|
||||
<>
|
||||
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
|
||||
{line.bill.invoice_number}
|
||||
</Link>
|
||||
</>
|
||||
) : (
|
||||
`${line.bill.invoice_number}`
|
||||
)}
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_price")}: `}
|
||||
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_cost")}: `}
|
||||
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<DateFormatter>{line.bill.date}</DateFormatter>
|
||||
</Col>
|
||||
<Col span={4}> {line.bill.vendor.name}</Col>
|
||||
</Row>
|
||||
)
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: "no-orders",
|
||||
children: t("bills.labels.nobilllines")
|
||||
}
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</FeatureWrapper>
|
||||
<Col md={24} lg={24}>
|
||||
<TaskListContainer
|
||||
parentJobId={jobid}
|
||||
relationshipType={"joblineid"}
|
||||
relationshipId={jobline.id}
|
||||
query={{ QUERY_JOBLINE_TASKS_PAGINATED }}
|
||||
titleTranslation="tasks.titles.job_tasks"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,21 @@ import { Button, Form, notification, Popover, Tooltip } from "antd";
|
||||
import axios from "axios";
|
||||
import { t } from "i18next";
|
||||
import React, { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { UPDATE_LINE_PPC } from "../../graphql/jobs-lines.queries";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
|
||||
|
||||
export default function JobLinesPartPriceChange({ job, line, refetch }) {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
technician: selectTechnician
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export function JobLinesPartPriceChange({ job, line, refetch, technician }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updatePartPrice] = useMutation(UPDATE_LINE_PPC);
|
||||
|
||||
@@ -52,7 +60,7 @@ export default function JobLinesPartPriceChange({ job, line, refetch }) {
|
||||
}
|
||||
};
|
||||
|
||||
const popcontent = InstanceRenderManager({
|
||||
const popcontent = !technician && InstanceRenderManager({
|
||||
imex: null,
|
||||
rome: (
|
||||
<Form layout="vertical" onFinish={handleFinish} initialValues={{ act_price: line.act_price }}>
|
||||
@@ -95,3 +103,4 @@ export default function JobLinesPartPriceChange({ job, line, refetch }) {
|
||||
</JobLineConvertToLabor>
|
||||
);
|
||||
}
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesPartPriceChange);
|
||||
|
||||
@@ -31,14 +31,17 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
|
||||
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import _ from "lodash";
|
||||
import { FaTasks } from "react-icons/fa";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
|
||||
import JobLineBulkAssignComponent from "../job-line-bulk-assign/job-line-bulk-assign.component";
|
||||
import JobLineDispatchButton from "../job-line-dispatch-button/job-line-dispatch-button.component";
|
||||
import JoblineTeamAssignment from "../job-line-team-assignment/job-line-team-assignmnent.component";
|
||||
import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-send-parts-price-change.component";
|
||||
import PartsOrderDrawer from "../parts-order-list-table/parts-order-list-table-drawer.component";
|
||||
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||
import JobLinesExpander from "./job-lines-expander.component";
|
||||
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
|
||||
@@ -52,7 +55,9 @@ const mapStateToProps = createStructuredSelector({
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setJobLineEditContext: (context) => dispatch(setModalContext({ context: context, modal: "jobLineEdit" })),
|
||||
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" }))
|
||||
setPartsReceiveContext: (context) => dispatch(setModalContext({ context: context, modal: "partsReceive" })),
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
export function JobLinesComponent({
|
||||
@@ -60,6 +65,7 @@ export function JobLinesComponent({
|
||||
jobRO,
|
||||
technician,
|
||||
setPartsOrderContext,
|
||||
setPartsReceiveContext,
|
||||
loading,
|
||||
refetch,
|
||||
jobLines,
|
||||
@@ -67,7 +73,12 @@ export function JobLinesComponent({
|
||||
job,
|
||||
setJobLineEditContext,
|
||||
form,
|
||||
setBillEnterContext
|
||||
setBillEnterContext,
|
||||
setTaskUpsertContext,
|
||||
billsQuery,
|
||||
handleBillOnRowClick,
|
||||
handlePartsOrderOnRowClick,
|
||||
handlePartsDispatchOnRowClick
|
||||
}) {
|
||||
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
|
||||
const {
|
||||
@@ -194,7 +205,6 @@ export function JobLinesComponent({
|
||||
onFilter: (value, record) => value.includes(record.part_type),
|
||||
render: (text, record) => (record.part_type ? t(`joblines.fields.part_types.${record.part_type}`) : null)
|
||||
},
|
||||
|
||||
{
|
||||
title: t("joblines.fields.act_price"),
|
||||
dataIndex: "act_price",
|
||||
@@ -209,7 +219,6 @@ export function JobLinesComponent({
|
||||
dataIndex: "part_qty",
|
||||
key: "part_qty"
|
||||
},
|
||||
|
||||
// {
|
||||
// title: t('joblines.fields.tax_part'),
|
||||
// dataIndex: 'tax_part',
|
||||
@@ -318,7 +327,7 @@ export function JobLinesComponent({
|
||||
key: "actions",
|
||||
render: (text, record) => (
|
||||
<Space>
|
||||
{(record.manual_line || jobIsPrivate) && (
|
||||
{(record.manual_line || jobIsPrivate) && !technician && (
|
||||
<>
|
||||
<Button
|
||||
disabled={jobRO}
|
||||
@@ -331,6 +340,23 @@ export function JobLinesComponent({
|
||||
>
|
||||
<EditFilled />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
title={t("tasks.buttons.create")}
|
||||
onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
joblineid: record.id
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaTasks />
|
||||
</Button>
|
||||
{(record.manual_line || jobIsPrivate) && !technician && (
|
||||
<>
|
||||
<Button
|
||||
disabled={jobRO}
|
||||
onClick={async () => {
|
||||
@@ -415,6 +441,15 @@ export function JobLinesComponent({
|
||||
return (
|
||||
<div>
|
||||
<PartsOrderModalContainer />
|
||||
{!technician && (
|
||||
<PartsOrderDrawer
|
||||
job={job}
|
||||
billsQuery={billsQuery}
|
||||
handleOnRowClick={handlePartsOrderOnRowClick}
|
||||
setPartsReceiveContext={setPartsReceiveContext}
|
||||
setTaskUpsertContext={setTaskUpsertContext}
|
||||
/>
|
||||
)}
|
||||
<PageHeader
|
||||
title={t("jobs.labels.estimatelines")}
|
||||
extra={
|
||||
@@ -452,7 +487,7 @@ export function JobLinesComponent({
|
||||
vendorid: bodyshop.inhousevendorid,
|
||||
invoice_number: "ih",
|
||||
isinhouse: true,
|
||||
date: new dayjs(),
|
||||
date: dayjs(),
|
||||
total: 0,
|
||||
billlines: selectedLines.map((p) => {
|
||||
return {
|
||||
@@ -531,7 +566,7 @@ export function JobLinesComponent({
|
||||
>
|
||||
{t("joblines.actions.new")}
|
||||
</Button>
|
||||
{bodyshop.region_config.toLowerCase().startsWith("us") && <JobSendPartPriceChangeComponent job={job} />}
|
||||
{InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} disabled={technician} /> })}
|
||||
<JobCreateIOU job={job} selectedJobLines={selectedLines} />
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import React, { useMemo, useState } from "react";
|
||||
import JobLinesComponent from "./job-lines.component";
|
||||
|
||||
function JobLinesContainer({ job, joblines, refetch, form, ...rest }) {
|
||||
function JobLinesContainer({
|
||||
job,
|
||||
joblines,
|
||||
billsQuery,
|
||||
handleBillOnRowClick,
|
||||
handlePartsOrderOnRowClick,
|
||||
handlePartsDispatchOnRowClick,
|
||||
refetch,
|
||||
form,
|
||||
...rest
|
||||
}) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const jobLines = useMemo(() => {
|
||||
@@ -22,7 +32,19 @@ function JobLinesContainer({ job, joblines, refetch, form, ...rest }) {
|
||||
}, [joblines, searchText]);
|
||||
|
||||
return (
|
||||
<JobLinesComponent refetch={refetch} jobLines={jobLines} setSearchText={setSearchText} job={job} form={form} />
|
||||
<div>
|
||||
<JobLinesComponent
|
||||
refetch={refetch}
|
||||
jobLines={jobLines}
|
||||
billsQuery={billsQuery}
|
||||
handleBillOnRowClick={handleBillOnRowClick}
|
||||
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
|
||||
handlePartsDispatchOnRowClick={handlePartsDispatchOnRowClick}
|
||||
setSearchText={setSearchText}
|
||||
job={job}
|
||||
form={form}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,17 +11,18 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
technician: selectTechnician
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobLineConvertToLabor);
|
||||
|
||||
export function JobLineConvertToLabor({ children, jobline, job, insertAuditTrail, ...otherBtnProps }) {
|
||||
export function JobLineConvertToLabor({ children, jobline, job, insertAuditTrail, technician, ...otherBtnProps }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -154,7 +155,7 @@ export function JobLineConvertToLabor({ children, jobline, job, insertAuditTrail
|
||||
setLoading(true);
|
||||
|
||||
form.setFieldsValue({
|
||||
// date: new dayjs(),
|
||||
// date: dayjs(),
|
||||
// bodyhrs: Math.round(v.bodyhrs * 10) / 10,
|
||||
// painthrs: Math.round(v.painthrs * 10) / 10,
|
||||
});
|
||||
@@ -165,7 +166,7 @@ export function JobLineConvertToLabor({ children, jobline, job, insertAuditTrail
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
{jobline.act_price !== 0 && (
|
||||
{jobline.act_price !== 0 && !technician && (
|
||||
<Popover disabled={jobline.convertedtolbr} content={overlay} open={visibility} placement="bottom">
|
||||
<Tooltip title={t("joblines.actions.converttolabor")}>
|
||||
<Button
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
QUERY_SCOREBOARD_ENTRY,
|
||||
UPDATE_SCOREBOARD_ENTRY
|
||||
} from "../../graphql/scoreboard.queries";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -86,7 +86,7 @@ export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps })
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.bodyhrs")}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { useLazyQuery } from "@apollo/client";
|
||||
import { Select, Space, Spin, Tag } from "antd";
|
||||
import _ from "lodash";
|
||||
@@ -6,8 +7,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import { SearchOutlined } from "@ant-design/icons";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -75,7 +74,7 @@ const JobSearchSelect = (
|
||||
filterOption={false}
|
||||
onSearch={handleSearch}
|
||||
//loading={loading || idLoading}
|
||||
suffixIcon={loading && <Spin />}
|
||||
suffixIcon={(loading || idLoading) && <Spin />}
|
||||
notFoundContent={loading ? <LoadingOutlined /> : null}
|
||||
{...restProps}
|
||||
>
|
||||
@@ -86,9 +85,9 @@ const JobSearchSelect = (
|
||||
<span>
|
||||
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
|
||||
o.ro_number || t("general.labels.na")
|
||||
} | ${OwnerNameDisplayFunction(o)} | ${
|
||||
o.v_model_yr || ""
|
||||
} ${o.v_make_desc || ""} ${o.v_model_desc || ""}`}
|
||||
} | ${OwnerNameDisplayFunction(o)} | ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${
|
||||
o.v_model_desc || ""
|
||||
}`}
|
||||
</span>
|
||||
<Tag>
|
||||
<strong>{o.status}</strong>
|
||||
|
||||
@@ -3,7 +3,7 @@ import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function JobSendPartPriceChangeComponent({ job }) {
|
||||
export default function JobSendPartPriceChangeComponent({ job, disabled }) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleClick = async () => {
|
||||
@@ -24,7 +24,7 @@ export default function JobSendPartPriceChangeComponent({ job }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleClick} loading={loading}>
|
||||
<Button onClick={handleClick} loading={loading} disabled={disabled}>
|
||||
{t("jobs.actions.sendpartspricechange")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -3,8 +3,8 @@ import Dinero from "dinero.js";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
|
||||
export default function JobTotalsTableLabor({ job }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -56,16 +56,49 @@ export default function JobTotalsTableLabor({ job }) {
|
||||
sortOrder: state.sortedInfo.columnKey === "mod_lb_hrs" && state.sortedInfo.order,
|
||||
render: (text, record) => record.hours.toFixed(1)
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.total"),
|
||||
dataIndex: "total",
|
||||
key: "total",
|
||||
align: "right",
|
||||
sorter: (a, b) => a.total.amount - b.total.amount,
|
||||
sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
|
||||
|
||||
render: (text, record) => Dinero(record.total).toFormat()
|
||||
}
|
||||
...InstanceRenderManager({
|
||||
imex: [
|
||||
{
|
||||
title: t("joblines.fields.total"),
|
||||
dataIndex: "total",
|
||||
key: "total",
|
||||
align: "right",
|
||||
sorter: (a, b) => a.total.amount - b.total.amount,
|
||||
sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
|
||||
render: (text, record) => Dinero(record.total).toFormat()
|
||||
}
|
||||
],
|
||||
rome: [
|
||||
{
|
||||
title: t("joblines.fields.amount"),
|
||||
dataIndex: "base",
|
||||
key: "base",
|
||||
align: "right",
|
||||
sorter: (a, b) => a.base.amount - b.base.amount,
|
||||
sortOrder: state.sortedInfo.columnKey === "base" && state.sortedInfo.order,
|
||||
render: (text, record) => Dinero(record.base).toFormat()
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.adjustment"),
|
||||
dataIndex: "adjustment",
|
||||
key: "adjustment",
|
||||
align: "right",
|
||||
sorter: (a, b) => a.adjustment.amount - b.adjustment.amount,
|
||||
sortOrder: state.sortedInfo.columnKey === "adjustment" && state.sortedInfo.order,
|
||||
render: (text, record) => Dinero(record.adjustment).toFormat()
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.total"),
|
||||
dataIndex: "total",
|
||||
key: "total",
|
||||
align: "right",
|
||||
sorter: (a, b) => a.total.amount - b.total.amount,
|
||||
sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
|
||||
render: (text, record) => Dinero(record.total).toFormat()
|
||||
}
|
||||
],
|
||||
promanager: "USE_ROME"
|
||||
})
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
@@ -91,6 +124,16 @@ export default function JobTotalsTableLabor({ job }) {
|
||||
<Table.Summary.Cell>
|
||||
{(job.job_totals.rates.mapa.hours + job.job_totals.rates.mash.hours).toFixed(1)}
|
||||
</Table.Summary.Cell>
|
||||
{InstanceRenderManager({
|
||||
imex: null,
|
||||
rome: (
|
||||
<>
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell />
|
||||
</>
|
||||
),
|
||||
promanager: "USE_ROME"
|
||||
})}
|
||||
<Table.Summary.Cell align="right">
|
||||
<strong>{Dinero(job.job_totals.rates.rates_subtotal).toFormat()}</strong>
|
||||
</Table.Summary.Cell>
|
||||
@@ -122,7 +165,29 @@ export default function JobTotalsTableLabor({ job }) {
|
||||
<CurrencyFormatter>{job.job_totals.rates.mapa.rate}</CurrencyFormatter>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>{job.job_totals.rates.mapa.hours.toFixed(1)}</Table.Summary.Cell>
|
||||
<Table.Summary.Cell align="right">{Dinero(job.job_totals.rates.mapa.total).toFormat()}</Table.Summary.Cell>
|
||||
{InstanceRenderManager({
|
||||
imex: (
|
||||
<>
|
||||
<Table.Summary.Cell align="right">
|
||||
{Dinero(job.job_totals.rates.mapa.total).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
</>
|
||||
),
|
||||
rome: (
|
||||
<>
|
||||
<Table.Summary.Cell align="right">
|
||||
{Dinero(job.job_totals.rates.mapa.base).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell align="right">
|
||||
{Dinero(job.job_totals.rates.mapa.adjustment).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell align="right">
|
||||
{Dinero(job.job_totals.rates.mapa.total).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
</>
|
||||
),
|
||||
promanager: "USE_ROME"
|
||||
})}
|
||||
</Table.Summary.Row>
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
@@ -151,7 +216,29 @@ export default function JobTotalsTableLabor({ job }) {
|
||||
<CurrencyFormatter>{job.job_totals.rates.mash.rate}</CurrencyFormatter>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>{job.job_totals.rates.mash.hours.toFixed(1)}</Table.Summary.Cell>
|
||||
<Table.Summary.Cell align="right">{Dinero(job.job_totals.rates.mash.total).toFormat()}</Table.Summary.Cell>
|
||||
{InstanceRenderManager({
|
||||
imex: (
|
||||
<>
|
||||
<Table.Summary.Cell align="right">
|
||||
{Dinero(job.job_totals.rates.mash.total).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
</>
|
||||
),
|
||||
rome: (
|
||||
<>
|
||||
<Table.Summary.Cell align="right">
|
||||
{Dinero(job.job_totals.rates.mash.base).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell align="right">
|
||||
{Dinero(job.job_totals.rates.mash.adjustment).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell align="right">
|
||||
{Dinero(job.job_totals.rates.mash.total).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
</>
|
||||
),
|
||||
promanager: "USE_ROME"
|
||||
})}
|
||||
</Table.Summary.Row>
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
@@ -159,6 +246,16 @@ export default function JobTotalsTableLabor({ job }) {
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell />
|
||||
{InstanceRenderManager({
|
||||
imex: null,
|
||||
rome: (
|
||||
<>
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell />
|
||||
</>
|
||||
),
|
||||
promanager: "USE_ROME"
|
||||
})}
|
||||
<Table.Summary.Cell align="right">
|
||||
<strong>{Dinero(job.job_totals.rates.subtotal).toFormat()}</strong>
|
||||
</Table.Summary.Cell>
|
||||
|
||||
@@ -141,10 +141,14 @@ 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
|
||||
@@ -165,6 +169,8 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||
bold: true
|
||||
}
|
||||
];
|
||||
// TODO: was removed by Patrick during a CI bug fix.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);
|
||||
|
||||
const columns = [
|
||||
|
||||
@@ -5,7 +5,6 @@ import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
@@ -20,7 +19,14 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange);
|
||||
@@ -87,7 +93,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
||||
<FormFieldsChanged form={form} />
|
||||
<LayoutFormRow header={t("jobs.forms.estdates")}>
|
||||
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
||||
<FormDatePicker format="MM/DD/YYYY" />
|
||||
<DateTimePicker format="MM/DD/YYYY" isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.date_towin")} name="date_towin">
|
||||
<DateTimePicker />
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button, Space, notification } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DELETE_DELIVERY_CHECKLIST, DELETE_INTAKE_CHECKLIST } from "../../graphql/jobs.queries";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
export default function JobAdminDeleteIntake({ job }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -47,16 +48,22 @@ export default function JobAdminDeleteIntake({ job }) {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
const InstanceRender = InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: "USE_IMEX",
|
||||
promanager: false
|
||||
});
|
||||
|
||||
return InstanceRender ? (
|
||||
<>
|
||||
<Space wrap>
|
||||
<Button loading={loading} onClick={handleDelete} disabled={!job.intakechecklist}>
|
||||
{t("jobs.labels.deleteintake")}
|
||||
</Button>
|
||||
<Button loading={loading} onClick={handleDeleteDelivery} disabled={!job.deliverychecklist}>
|
||||
<Button loading={loading} onClick={handleDeleteDelivery} disabled={!job.deliverchecklist}>
|
||||
{t("jobs.labels.deletedelivery")}
|
||||
</Button>
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
) : null;
|
||||
}
|
||||
|
||||
@@ -15,23 +15,43 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
|
||||
const linesToUpdate = [];
|
||||
|
||||
newLines.forEach((newLine) => {
|
||||
const matchingIndex = existingLines.findIndex((eL) => eL.unq_seq === newLine.unq_seq);
|
||||
const matchingIndexLines = [];
|
||||
existingLines.forEach((eL, index) => {
|
||||
if (eL.unq_seq === newLine.unq_seq) {
|
||||
matchingIndexLines.push({ index, record: eL });
|
||||
}
|
||||
});
|
||||
|
||||
//Should do a check to make sure there is only 1 matching unq sequence number.
|
||||
|
||||
if (matchingIndex >= 0) {
|
||||
//Found a relevant matching line. Add it to lines to update.
|
||||
linesToUpdate.push({
|
||||
id: existingLines[matchingIndex].id,
|
||||
newData: { ...newLine, removed: false, act_price_before_ppc: null }
|
||||
});
|
||||
|
||||
//Splice out item we found for performance.
|
||||
existingLines.splice(matchingIndex, 1);
|
||||
} else {
|
||||
if (matchingIndexLines.length === 0) {
|
||||
//Didn't find a match. Must be a new line.
|
||||
linesToInsert.push(newLine);
|
||||
}
|
||||
//If we find only 1, we can use the old logic.
|
||||
else if (matchingIndexLines.length === 1) {
|
||||
//Found a relevant matching line. Add it to lines to update.
|
||||
linesToUpdate.push({
|
||||
id: matchingIndexLines[0].record.id,
|
||||
newData: { ...newLine, removed: false, act_price_before_ppc: null }
|
||||
});
|
||||
//Splice out item we found for performance.
|
||||
existingLines.splice(matchingIndexLines[0].index, 1);
|
||||
} else if (matchingIndexLines.length === 2) {
|
||||
//if we find 2, we need to separate it out by the non-refinish lines and splice one out.
|
||||
//Find the mathcing one depending on whether this is the refiniish one or not.
|
||||
const matchingLine = matchingIndexLines.find((i) =>
|
||||
newLine.mod_lbr_ty === "LAR" ? i.record.mod_lbr_ty === "LAR" : i.record.mod_lbr_ty !== "LAR"
|
||||
);
|
||||
|
||||
linesToUpdate.push({
|
||||
id: matchingLine.record.id,
|
||||
newData: { ...newLine, removed: false, act_price_before_ppc: null }
|
||||
});
|
||||
//Splice out item we found for performance.
|
||||
existingLines.splice(matchingLine.index, 1);
|
||||
} else {
|
||||
//We found more than 2 matching lines. We should never get here. Throw a warning and back out!
|
||||
throw new Error("Too many matching lines found. Ensure EMS file is valid.");
|
||||
}
|
||||
});
|
||||
|
||||
//Wahtever is left in the existing lines, are lines that should be removed.
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Col, Row, notification } from "antd";
|
||||
import Axios from "axios";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -24,6 +23,8 @@ import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selecto
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import confirmDialog from "../../utils/asyncConfirm";
|
||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||
import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
|
||||
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
|
||||
@@ -32,7 +33,6 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
|
||||
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
|
||||
import HeaderFields from "./jobs-available-supplement.headerfields";
|
||||
import JobsAvailableTableComponent from "./jobs-available-table.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -237,7 +237,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
|
||||
executeFunction: true,
|
||||
rome: ResolveCCCLineIssues,
|
||||
promanager: ResolveCCCLineIssues,
|
||||
args: [(supp, bodyshop)]
|
||||
args: [supp, bodyshop]
|
||||
});
|
||||
|
||||
await InstanceRenderManager({
|
||||
@@ -580,12 +580,13 @@ function ResolveCCCLineIssues(estData, bodyshop) {
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
args: [],
|
||||
promanager: () => {
|
||||
rome: () => {
|
||||
if (line.mod_lbr_ty === "LAET" || line.mod_lbr_ty === "LAUT") {
|
||||
// line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`;
|
||||
line.mod_lbr_ty = "LAR";
|
||||
}
|
||||
}
|
||||
},
|
||||
promanager: "USE_ROME"
|
||||
});
|
||||
});
|
||||
|
||||
@@ -608,13 +609,12 @@ function ResolveCCCLineIssues(estData, bodyshop) {
|
||||
//Web Est seems to have additional costs with UNQ_SEQ 0. Keep them all?
|
||||
if (line.unq_seq === 0) return;
|
||||
if (index0ActPrice !== line.act_price) {
|
||||
line.notes += ` | Price override.`;
|
||||
// line.notes += ` | Price override.`;
|
||||
return;
|
||||
}
|
||||
const indexInEstData = estData.joblines.data.findIndex((l) => l.unq_seq === line.unq_seq);
|
||||
estData.joblines.data[
|
||||
indexInEstData
|
||||
].notes += ` | Scrubbed due to the line_ref issue. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`;
|
||||
//estData.joblines.data[indexInEstData].notes +=
|
||||
// ` | Act Price delete. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`;
|
||||
estData.joblines.data[indexInEstData].act_price = 0;
|
||||
estData.joblines.data[indexInEstData].db_price = 0;
|
||||
});
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import {
|
||||
Collapse,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
} from "antd";
|
||||
import { Collapse, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
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";
|
||||
@@ -29,6 +20,7 @@ import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.c
|
||||
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -61,10 +53,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.regie_number")}
|
||||
name="regie_number"
|
||||
>
|
||||
<Form.Item label={t("jobs.fields.regie_number")} name="regie_number">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||
@@ -116,7 +105,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
<FormItemEmail email={getFieldValue("ins_ea")} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||
<FormDatePicker />
|
||||
<DateTimePicker isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
|
||||
<Input />
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Form, Input } from "antd";
|
||||
import React, { useContext } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
|
||||
export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
||||
const [state] = useContext(JobCreateContext);
|
||||
@@ -113,7 +113,7 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
||||
<Input disabled={!state.vehicle.new} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("vehicles.fields.v_prod_dt")} name={["vehicle", "data", "v_prod_dt"]}>
|
||||
<FormDatePicker disabled={!state.vehicle.new} />
|
||||
<DateTimePicker isDateOnly disabled={!state.vehicle.new} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
@@ -30,7 +29,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
<div>
|
||||
<FormRow header={t("jobs.forms.estdates")}>
|
||||
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
||||
<FormDatePicker disabled={jobRO} />
|
||||
<DateTimePicker disabled={jobRO} isDateOnly />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
|
||||
<DateTimePicker disabled={jobRO} />
|
||||
@@ -45,7 +44,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
|
||||
<FormRow header={t("jobs.forms.scheddates")}>
|
||||
<Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled">
|
||||
<FormDatePicker disabled={jobRO} />
|
||||
<DateTimePicker disabled={jobRO} isDateOnly />
|
||||
</Form.Item>
|
||||
<Tooltip title={t("jobs.labels.scheduledinchange")}>
|
||||
<Form.Item label={t("jobs.fields.scheduled_in")} name="scheduled_in">
|
||||
@@ -85,7 +84,6 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
rules={[
|
||||
{
|
||||
required: jobInPostProduction
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
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";
|
||||
@@ -13,6 +12,7 @@ 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,
|
||||
@@ -152,7 +152,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||
<FormDatePicker disabled={jobRO} />
|
||||
<DateTimePicker isDateOnly disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
|
||||
<Input disabled={jobRO} />
|
||||
@@ -189,7 +189,10 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
</Col>
|
||||
<Col {...lossColDamage}>
|
||||
{job.area_of_damage ? (
|
||||
<Car dmg1={job.area_of_damage.impact1} dmg2={job.area_of_damage.impact2} />
|
||||
<Car
|
||||
dmg1={job.area_of_damage.impact1 && job.area_of_damage.impact1.padStart(2, "0")}
|
||||
dmg2={job.area_of_damage.impact2 && job.area_of_damage.impact2.padStart(2, "0")}
|
||||
/>
|
||||
) : (
|
||||
t("jobs.errors.nodamage")
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { DownCircleFilled } from "@ant-design/icons";
|
||||
import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -8,27 +11,24 @@ import { Link, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
|
||||
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
|
||||
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
||||
import axios from "axios";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -39,13 +39,57 @@ const mapStateToProps = createStructuredSelector({
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||
setJobCostingContext: (context) => dispatch(setModalContext({ context: context, modal: "jobCosting" })),
|
||||
setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
||||
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||
setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
|
||||
setJobCostingContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "jobCosting"
|
||||
})
|
||||
),
|
||||
setTimeTicketContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "timeTicket"
|
||||
})
|
||||
),
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "cardPayment"
|
||||
})
|
||||
),
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
),
|
||||
setTimeTicketTaskContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "timeTicketTask"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "taskUpsert"
|
||||
})
|
||||
),
|
||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
setMessage: (text) => dispatch(setMessage(text))
|
||||
@@ -67,7 +111,8 @@ export function JobsDetailHeaderActions({
|
||||
setEmailOptions,
|
||||
openChatByPhone,
|
||||
setMessage,
|
||||
setTimeTicketTaskContext
|
||||
setTimeTicketTaskContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -596,6 +641,7 @@ export function JobsDetailHeaderActions({
|
||||
const menuItems = [
|
||||
{
|
||||
key: "schedule",
|
||||
id: "job-actions-schedule",
|
||||
disabled: !jobInPreProduction || !job.converted || jobRO,
|
||||
label: t("jobs.actions.schedule"),
|
||||
onClick: () => {
|
||||
@@ -612,6 +658,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "cancelallappointments",
|
||||
id: "job-actions-cancelallappointments",
|
||||
onClick: () => {
|
||||
if (job.status !== bodyshop.md_ro_statuses.default_scheduled) {
|
||||
return;
|
||||
@@ -625,6 +672,7 @@ export function JobsDetailHeaderActions({
|
||||
imex: [
|
||||
{
|
||||
key: "intake",
|
||||
id: "job-actions-intake",
|
||||
disabled: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO,
|
||||
label:
|
||||
!!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO ? (
|
||||
@@ -635,6 +683,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "deliver",
|
||||
id: "job-actions-deliver",
|
||||
disabled: !jobInProduction || jobRO,
|
||||
label: !jobInProduction ? (
|
||||
t("jobs.actions.deliver")
|
||||
@@ -644,6 +693,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "checklist",
|
||||
id: "job-actions-checklist",
|
||||
disabled: !job.converted,
|
||||
label: <Link to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link>
|
||||
}
|
||||
@@ -652,6 +702,7 @@ export function JobsDetailHeaderActions({
|
||||
promanager: [
|
||||
{
|
||||
key: "toggleproduction",
|
||||
id: "job-actions-toggleproduction",
|
||||
disabled: !job.converted || jobRO,
|
||||
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} />
|
||||
}
|
||||
@@ -665,6 +716,7 @@ export function JobsDetailHeaderActions({
|
||||
? [
|
||||
{
|
||||
key: "entertimetickets",
|
||||
id: "job-actions-entertimetickets",
|
||||
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
|
||||
label: t("timetickets.actions.enter"),
|
||||
onClick: () => {
|
||||
@@ -688,6 +740,7 @@ export function JobsDetailHeaderActions({
|
||||
if (bodyshop.md_tasks_presets.enable_tasks) {
|
||||
menuItems.push({
|
||||
key: "claimtimetickettasks",
|
||||
id: "job-actions-claimtimetickettasks",
|
||||
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
|
||||
onClick: () => {
|
||||
setTimeTicketTaskContext({
|
||||
@@ -701,6 +754,7 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
menuItems.push({
|
||||
key: "enterpayments",
|
||||
id: "job-actions-enterpayments",
|
||||
disabled: !job.converted,
|
||||
label: t("menus.header.enterpayment"),
|
||||
onClick: () => {
|
||||
@@ -716,22 +770,24 @@ export function JobsDetailHeaderActions({
|
||||
if (ImEXPay.treatment === "on") {
|
||||
menuItems.push({
|
||||
key: "entercardpayments",
|
||||
id: "job-actions-entercardpayments",
|
||||
disabled: !job.converted,
|
||||
label: t("menus.header.entercardpayment"),
|
||||
onClick: () => {
|
||||
logImEXEvent("job_header_enter_card_payment");
|
||||
|
||||
setCardPaymentContext({
|
||||
actions: {},
|
||||
actions: { refetch },
|
||||
context: { jobid: job.id }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (HasFeatureAccess({ featureName: "courtesycars" })) {
|
||||
if (HasFeatureAccess({ featureName: "courtesycars", bodyshop })) {
|
||||
menuItems.push({
|
||||
key: "cccontract",
|
||||
id: "job-actions-cccontract",
|
||||
disabled: jobRO || !job.converted,
|
||||
label: (
|
||||
<Link state={{ jobId: job.id }} to="/manage/courtesycars/contracts/new">
|
||||
@@ -741,16 +797,29 @@ export function JobsDetailHeaderActions({
|
||||
});
|
||||
}
|
||||
|
||||
menuItems.push({
|
||||
key: "createtask",
|
||||
id: "job-actions-createtask",
|
||||
label: t("menus.header.create_task"),
|
||||
onClick: () =>
|
||||
setTaskUpsertContext({
|
||||
actions: {},
|
||||
context: { jobid: job.id }
|
||||
})
|
||||
});
|
||||
|
||||
menuItems.push(
|
||||
job.inproduction
|
||||
? {
|
||||
key: "addtoproduction",
|
||||
key: "removefromproduction",
|
||||
id: "job-actions-removefromproduction",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.removefromproduction"),
|
||||
onClick: () => AddToProduction(client, job.id, refetch, true)
|
||||
}
|
||||
: {
|
||||
key: "addtoproduction",
|
||||
id: "job-actions-addtoproduction",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.addtoproduction"),
|
||||
onClick: () => AddToProduction(client, job.id, refetch)
|
||||
@@ -760,12 +829,14 @@ export function JobsDetailHeaderActions({
|
||||
menuItems.push(
|
||||
{
|
||||
key: "togglesuspend",
|
||||
id: "job-actions-togglesuspend",
|
||||
onClick: handleSuspend,
|
||||
label: job.suspended ? t("production.actions.unsuspend") : t("production.actions.suspend")
|
||||
},
|
||||
{
|
||||
key: "toggleAlert",
|
||||
onClick: handleAlertToggle,
|
||||
id: "job-actions-togglealert",
|
||||
label:
|
||||
job.production_vars && job.production_vars.alert
|
||||
? t("production.labels.alertoff")
|
||||
@@ -777,6 +848,7 @@ export function JobsDetailHeaderActions({
|
||||
children: [
|
||||
{
|
||||
key: "duplicate",
|
||||
id: "job-actions-duplicate",
|
||||
label: (
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.duplicateconfirm")}
|
||||
@@ -792,6 +864,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "duplicatenolines",
|
||||
id: "job-actions-duplicatenolines",
|
||||
label: (
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.duplicateconfirm")}
|
||||
@@ -815,6 +888,7 @@ export function JobsDetailHeaderActions({
|
||||
? [
|
||||
{
|
||||
key: "postbills",
|
||||
id: "job-actions-postbills",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.postbills"),
|
||||
onClick: () => {
|
||||
@@ -833,6 +907,7 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
{
|
||||
key: "addtopartsqueue",
|
||||
id: "job-actions-addtopartsqueue",
|
||||
disabled: !job.converted || !jobInProduction || jobRO,
|
||||
label: t("jobs.actions.addtopartsqueue"),
|
||||
onClick: async () => {
|
||||
@@ -858,6 +933,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "closejob",
|
||||
id: "job-actions-closejob",
|
||||
disabled: !jobInPostProduction,
|
||||
label: !jobInPostProduction ? (
|
||||
t("menus.jobsactions.closejob")
|
||||
@@ -873,6 +949,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "admin",
|
||||
id: "job-actions-admin",
|
||||
label: (
|
||||
<Link
|
||||
to={{
|
||||
@@ -894,6 +971,7 @@ export function JobsDetailHeaderActions({
|
||||
) {
|
||||
menuItems.push({
|
||||
key: "exportcustdata",
|
||||
id: "job-actions-exportcustdata",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.exportcustdata"),
|
||||
onClick: handleExportCustData
|
||||
@@ -904,18 +982,21 @@ export function JobsDetailHeaderActions({
|
||||
const children = [
|
||||
{
|
||||
key: "email",
|
||||
id: "job-actions-email",
|
||||
disabled: !!!job.ownr_ea,
|
||||
label: t("general.labels.email"),
|
||||
onClick: handleCreateCsi
|
||||
},
|
||||
{
|
||||
key: "text",
|
||||
id: "job-actions-text",
|
||||
disabled: !!!job.ownr_ph1,
|
||||
label: t("general.labels.text"),
|
||||
onClick: handleCreateCsi
|
||||
},
|
||||
{
|
||||
key: "generate",
|
||||
id: "job-actions-generate",
|
||||
disabled: job.csiinvites && job.csiinvites.length > 0,
|
||||
label: t("jobs.actions.generatecsi"),
|
||||
onClick: handleCreateCsi
|
||||
@@ -949,6 +1030,7 @@ export function JobsDetailHeaderActions({
|
||||
}
|
||||
menuItems.push({
|
||||
key: "sendcsi",
|
||||
id: "job-actions-sendcsi",
|
||||
label: t("jobs.actions.sendcsi"),
|
||||
disabled: !job.converted,
|
||||
children
|
||||
@@ -957,6 +1039,7 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
menuItems.push({
|
||||
key: "jobcosting",
|
||||
id: "job-actions-jobcosting",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.labels.jobcosting"),
|
||||
onClick: () => {
|
||||
@@ -974,6 +1057,7 @@ export function JobsDetailHeaderActions({
|
||||
if (job && !job.converted) {
|
||||
menuItems.push({
|
||||
key: "deletejob",
|
||||
id: "job-actions-deletejob",
|
||||
label: (
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.deleteconfirm")}
|
||||
@@ -990,6 +1074,7 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
menuItems.push({
|
||||
key: "manualevent",
|
||||
id: "job-actions-manualevent",
|
||||
onClick: (e) => {
|
||||
setVisibility(true);
|
||||
},
|
||||
@@ -999,6 +1084,7 @@ export function JobsDetailHeaderActions({
|
||||
if (!jobRO && job.converted) {
|
||||
menuItems.push({
|
||||
key: "voidjob",
|
||||
id: "job-actions-voidjob",
|
||||
label: (
|
||||
<RbacWrapper action="jobs:void" noauth>
|
||||
<Popconfirm
|
||||
|
||||
@@ -1,55 +1,13 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { QUERY_BILLS_BY_JOBID } from "../../graphql/bills.queries";
|
||||
import JobsDetailPliComponent from "./jobs-detail-pli.component";
|
||||
|
||||
export default function JobsDetailPliContainer({ job }) {
|
||||
const billsQuery = useQuery(QUERY_BILLS_BY_JOBID, {
|
||||
variables: { jobid: job.id },
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useNavigate();
|
||||
|
||||
const handleBillOnRowClick = (record) => {
|
||||
if (record) {
|
||||
if (record.id) {
|
||||
search.billid = record.id;
|
||||
history({ search: queryString.stringify(search) });
|
||||
}
|
||||
} else {
|
||||
delete search.billid;
|
||||
history({ search: queryString.stringify(search) });
|
||||
}
|
||||
};
|
||||
|
||||
const handlePartsOrderOnRowClick = (record) => {
|
||||
if (record) {
|
||||
if (record.id) {
|
||||
search.partsorderid = record.id;
|
||||
history({ search: queryString.stringify(search) });
|
||||
}
|
||||
} else {
|
||||
delete search.partsorderid;
|
||||
history({ search: queryString.stringify(search) });
|
||||
}
|
||||
};
|
||||
|
||||
const handlePartsDispatchOnRowClick = (record) => {
|
||||
if (record) {
|
||||
if (record.id) {
|
||||
search.partsdispatchid = record.id;
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}
|
||||
} else {
|
||||
delete search.partsdispatchid;
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}
|
||||
};
|
||||
export default function JobsDetailPliContainer({
|
||||
job,
|
||||
billsQuery,
|
||||
handleBillOnRowClick,
|
||||
handlePartsOrderOnRowClick,
|
||||
handlePartsDispatchOnRowClick
|
||||
}) {
|
||||
return (
|
||||
<JobsDetailPliComponent
|
||||
job={job}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Collapse, Form, Switch } from "antd";
|
||||
import { Collapse, Form, InputNumber, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -17,6 +17,9 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
<Collapse defaultActiveKey={expanded && "rates"}>
|
||||
<Collapse.Panel forceRender header={t("jobs.labels.cieca_pfl")} key="cieca_pfl">
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAB")}>
|
||||
<Form.Item label={t("jobs.fields.cieca_pfl.lbr_adjp")} name={["cieca_pfl", "LAB", "lbr_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAB", "lbr_tax_in"]}
|
||||
@@ -24,6 +27,24 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
>
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_taxp")}
|
||||
name={["cieca_pfl", "LAB", "lbr_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["cieca_pfl", "LAB", "lbr_tax_in"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAB", "lbr_tx_in1"]}
|
||||
@@ -61,6 +82,9 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAD")}>
|
||||
<Form.Item label={t("jobs.fields.cieca_pfl.lbr_adjp")} name={["cieca_pfl", "LAD", "lbr_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAD", "lbr_tax_in"]}
|
||||
@@ -68,6 +92,24 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
>
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_taxp")}
|
||||
name={["cieca_pfl", "LAD", "lbr_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["cieca_pfl", "LAD", "lbr_tax_in"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAD", "lbr_tx_in1"]}
|
||||
@@ -105,6 +147,9 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAE")}>
|
||||
<Form.Item label={t("jobs.fields.cieca_pfl.lbr_adjp")} name={["cieca_pfl", "LAE", "lbr_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAE", "lbr_tax_in"]}
|
||||
@@ -112,6 +157,24 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
>
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_taxp")}
|
||||
name={["cieca_pfl", "LAE", "lbr_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["cieca_pfl", "LAE", "lbr_tax_in"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAE", "lbr_tx_in1"]}
|
||||
@@ -149,6 +212,9 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAF")}>
|
||||
<Form.Item label={t("jobs.fields.cieca_pfl.lbr_adjp")} name={["cieca_pfl", "LAF", "lbr_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAF", "lbr_tax_in"]}
|
||||
@@ -156,6 +222,24 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
>
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_taxp")}
|
||||
name={["cieca_pfl", "LAF", "lbr_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["cieca_pfl", "LAF", "lbr_tax_in"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAF", "lbr_tx_in1"]}
|
||||
@@ -193,6 +277,9 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAG")}>
|
||||
<Form.Item label={t("jobs.fields.cieca_pfl.lbr_adjp")} name={["cieca_pfl", "LAG", "lbr_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAG", "lbr_tax_in"]}
|
||||
@@ -200,6 +287,24 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
>
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_taxp")}
|
||||
name={["cieca_pfl", "LAG", "lbr_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["cieca_pfl", "LAG", "lbr_tax_in"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAG", "lbr_tx_in1"]}
|
||||
@@ -237,6 +342,9 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAM")}>
|
||||
<Form.Item label={t("jobs.fields.cieca_pfl.lbr_adjp")} name={["cieca_pfl", "LAM", "lbr_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAM", "lbr_tax_in"]}
|
||||
@@ -244,6 +352,24 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
>
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_taxp")}
|
||||
name={["cieca_pfl", "LAM", "lbr_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["cieca_pfl", "LAM", "lbr_tax_in"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAM", "lbr_tx_in1"]}
|
||||
@@ -281,6 +407,9 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAR")}>
|
||||
<Form.Item label={t("jobs.fields.cieca_pfl.lbr_adjp")} name={["cieca_pfl", "LAR", "lbr_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAR", "lbr_tax_in"]}
|
||||
@@ -288,6 +417,24 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
>
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_taxp")}
|
||||
name={["cieca_pfl", "LAR", "lbr_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["cieca_pfl", "LAR", "lbr_tax_in"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAR", "lbr_tx_in1"]}
|
||||
@@ -325,6 +472,9 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAS")}>
|
||||
<Form.Item label={t("jobs.fields.cieca_pfl.lbr_adjp")} name={["cieca_pfl", "LAS", "lbr_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAS", "lbr_tax_in"]}
|
||||
@@ -332,6 +482,24 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
>
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_taxp")}
|
||||
name={["cieca_pfl", "LAS", "lbr_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["cieca_pfl", "LAS", "lbr_tax_in"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAS", "lbr_tx_in1"]}
|
||||
@@ -369,6 +537,9 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAU")}>
|
||||
<Form.Item label={t("jobs.fields.cieca_pfl.lbr_adjp")} name={["cieca_pfl", "LAU", "lbr_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAU", "lbr_tax_in"]}
|
||||
@@ -376,6 +547,24 @@ export function JobsDetailRatesLabor({ jobRO, expanded, required = true, form })
|
||||
>
|
||||
<Switch disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_taxp")}
|
||||
name={["cieca_pfl", "LAU", "lbr_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["cieca_pfl", "LAU", "lbr_tax_in"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAU", "lbr_tx_in1"]}
|
||||
|
||||
@@ -23,7 +23,9 @@ export function JobsDetailRatesMaterials({ jobRO, expanded, required = true, for
|
||||
<Form.Item label={t("jobs.fields.materials.cal_opcode")} name={["materials", "MAPA", "cal_opcode"]}>
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("jobs.fields.materials.mat_adjp")} name={["materials", "MAPA", "mat_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.tax_ind")}
|
||||
name={["materials", "MAPA", "tax_ind"]}
|
||||
@@ -31,6 +33,24 @@ export function JobsDetailRatesMaterials({ jobRO, expanded, required = true, for
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_taxp")}
|
||||
name={["materials", "MAPA", "mat_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["materials", "MAPA", "tax_ind"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in1")}
|
||||
name={["materials", "MAPA", "mat_tx_in1"]}
|
||||
@@ -74,7 +94,9 @@ export function JobsDetailRatesMaterials({ jobRO, expanded, required = true, for
|
||||
<Form.Item label={t("jobs.fields.materials.cal_opcode")} name={["materials", "MASH", "cal_opcode"]}>
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={t("jobs.fields.materials.mat_adjp")} name={["materials", "MAPA", "mat_adjp"]}>
|
||||
<InputNumber min={-100} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.tax_ind")}
|
||||
name={["materials", "MASH", "tax_ind"]}
|
||||
@@ -82,6 +104,24 @@ export function JobsDetailRatesMaterials({ jobRO, expanded, required = true, for
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_taxp")}
|
||||
name={["materials", "MASH", "mat_taxp"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["materials", "MASH", "tax_ind"])
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in1")}
|
||||
name={["materials", "MASH", "mat_tx_in1"]}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -988,18 +989,24 @@ export function JobsDetailRatesParts({ jobRO, expanded, required = true, form })
|
||||
<Form.Item label={t("jobs.fields.tax_str_rt")} name="tax_str_rt">
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.tax_paint_mat_rt")} name="tax_paint_mat_rt">
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.tax_shop_mat_rt")} name="tax_shop_mat_rt">
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
{InstanceRenderManager({ imex: true, rome: false, promanager: "USE_ROME" }) ? (
|
||||
<>
|
||||
<Form.Item label={t("jobs.fields.tax_paint_mat_rt")} name="tax_paint_mat_rt">
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.tax_shop_mat_rt")} name="tax_shop_mat_rt">
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>{" "}
|
||||
</>
|
||||
) : null}
|
||||
<Form.Item label={t("jobs.fields.tax_sub_rt")} name="tax_sub_rt">
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.tax_lbr_rt")} name="tax_lbr_rt">
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
{InstanceRenderManager({ imex: true, rome: false, promanager: "USE_ROME" }) ? (
|
||||
<Form.Item label={t("jobs.fields.tax_lbr_rt")} name="tax_lbr_rt">
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
) : null}
|
||||
<Form.Item label={t("jobs.fields.tax_levies_rt")} name="tax_levies_rt">
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
@@ -37,7 +38,10 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sorter: search?.search
|
||||
? (a, b) =>
|
||||
parseInt((a.ro_number || "0").replace(/\D/g, "")) - parseInt((b.ro_number || "0").replace(/\D/g, ""))
|
||||
: true,
|
||||
sortOrder: sortcolumn === "ro_number" && sortorder,
|
||||
render: (text, record) => (
|
||||
<Link to={"/manage/jobs/" + record.id}>{record.ro_number || t("general.labels.na")}</Link>
|
||||
@@ -49,7 +53,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
key: "ownr_ln",
|
||||
ellipsis: true,
|
||||
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
|
||||
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
@@ -67,7 +70,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
|
||||
ellipsis: true,
|
||||
render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
},
|
||||
@@ -75,7 +77,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
|
||||
ellipsis: true,
|
||||
render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id} />
|
||||
},
|
||||
@@ -85,7 +86,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
key: "status",
|
||||
|
||||
ellipsis: true,
|
||||
sorter: true, // (a, b) => alphaSort(a.status, b.status),
|
||||
sorter: search?.search ? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses) : true,
|
||||
sortOrder: sortcolumn === "status" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.status || t("general.labels.na");
|
||||
@@ -96,12 +97,10 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
}),
|
||||
onFilter: (value, record) => value.includes(record.status)
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
@@ -118,7 +117,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
dataIndex: "plate_no",
|
||||
key: "plate_no",
|
||||
ellipsis: true,
|
||||
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
|
||||
sorter: search?.search ? (a, b) => alphaSort(a.plate_no, b.plate_no) : true,
|
||||
sortOrder: sortcolumn === "plate_no" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.plate_no ? record.plate_no : "";
|
||||
@@ -129,7 +128,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
ellipsis: true,
|
||||
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sorter: search?.search ? (a, b) => alphaSort(a.clm_no, b.clm_no) : true,
|
||||
sortOrder: sortcolumn === "clm_no" && sortorder,
|
||||
render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}`
|
||||
},
|
||||
@@ -143,8 +142,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.clm_total"),
|
||||
dataIndex: "clm_total",
|
||||
key: "clm_total",
|
||||
|
||||
sorter: true, //(a, b) => a.clm_total - b.clm_total,
|
||||
sorter: search?.search ? (a, b) => a.clm_total - b.clm_total : true,
|
||||
sortOrder: sortcolumn === "clm_total" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.clm_total ? (
|
||||
@@ -158,7 +156,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.owner_owing"),
|
||||
dataIndex: "owner_owing",
|
||||
key: "owner_owing",
|
||||
|
||||
render: (text, record) => <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,19 +16,15 @@ import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import { setJoyRideSteps } from "../../redux/application/application.actions";
|
||||
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps))
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export function JobsList({ bodyshop, setJoyRideSteps }) {
|
||||
export function JobsList({ bodyshop }) {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { selected } = searchParams;
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
@@ -176,7 +172,6 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.status)
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
@@ -255,8 +250,8 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.estimator"),
|
||||
dataIndex: "jobs.labels.estimator",
|
||||
key: "jobs.labels.estimator",
|
||||
dataIndex: "estimator",
|
||||
key: "estimator",
|
||||
ellipsis: true,
|
||||
responsive: ["xl"],
|
||||
sorter: (a, b) =>
|
||||
@@ -316,34 +311,6 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
||||
title={t("titles.bc.jobs-active")}
|
||||
extra={
|
||||
<Space wrap>
|
||||
{InstanceRenderManager({
|
||||
promanager: (
|
||||
<Button
|
||||
onClick={() =>
|
||||
setJoyRideSteps([
|
||||
{
|
||||
target: "#active-jobs-list",
|
||||
content: "This is where you will see all work coming in and currently here."
|
||||
},
|
||||
{
|
||||
target: "#header-jobs",
|
||||
spotlightClicks: true,
|
||||
disableOverlayClose: true,
|
||||
content:
|
||||
"The jobs menu lets you access additional pages to see more information. You can import new jobs, search all jobs, or manage your current production."
|
||||
},
|
||||
{
|
||||
target: "#header-jobs-available",
|
||||
content: "You can find jobs to import here."
|
||||
}
|
||||
])
|
||||
}
|
||||
>
|
||||
Start Walk Through
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification, Popconfirm } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { UPDATE_JOBS } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobMarkSelectedExported);
|
||||
|
||||
export function JobMarkSelectedExported({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
jobIds,
|
||||
disabled,
|
||||
loadingCallback,
|
||||
completedCallback,
|
||||
refetch,
|
||||
insertAuditTrail
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||
|
||||
const [updateJob] = useMutation(UPDATE_JOBS);
|
||||
const handleUpdate = async () => {
|
||||
setLoading(true);
|
||||
loadingCallback(true);
|
||||
const result = await updateJob({
|
||||
variables: {
|
||||
jobIds: jobIds,
|
||||
fields: {
|
||||
status: bodyshop.md_ro_statuses.default_exported || "Exported*",
|
||||
date_exported: new Date()
|
||||
}
|
||||
},
|
||||
update(cache) {}
|
||||
});
|
||||
|
||||
await insertExportLog({
|
||||
variables: {
|
||||
logs: jobIds.map((id) => {
|
||||
return {
|
||||
bodyshopid: bodyshop.id,
|
||||
jobid: id,
|
||||
successful: true,
|
||||
message: JSON.stringify([t("general.labels.markedexported")]),
|
||||
useremail: currentUser.email
|
||||
};
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
if (!result.errors) {
|
||||
notification["success"]({ message: t("jobs.successes.save") });
|
||||
result.data.update_jobs.returning.forEach((job) => {
|
||||
console.log("results job", job.id, "audit: ", AuditTrailMapping.admin_jobmarkexported());
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.admin_jobmarkexported(),
|
||||
type: "admin_jobmarkexported"
|
||||
});
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(result.errors)
|
||||
})
|
||||
});
|
||||
}
|
||||
loadingCallback(false);
|
||||
completedCallback && completedCallback([]);
|
||||
setLoading(false);
|
||||
refetch && refetch();
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popconfirm
|
||||
open={open}
|
||||
title={t("general.labels.areyousure")}
|
||||
onCancel={() => setOpen(false)}
|
||||
onConfirm={handleUpdate}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)} type="primary" danger>
|
||||
{t("jobs.actions.markasexported")}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user