Compare commits
538 Commits
feature/pa
...
rome/test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82fbe74723 | ||
|
|
1f2da44a3f | ||
|
|
85a3aeb335 | ||
|
|
80b7ae0e54 | ||
|
|
0529ac4478 | ||
|
|
3cafbebbee | ||
|
|
6f248d864e | ||
|
|
a4a84572b7 | ||
|
|
a45d0bb9f4 | ||
|
|
a2e0f9fbe7 | ||
|
|
e37dc0a18f | ||
|
|
0bc00d46cf | ||
|
|
e80e40bb76 | ||
|
|
a88c102b27 | ||
|
|
c691d44c44 | ||
|
|
ebb3a13ff5 | ||
|
|
3846b7c5fc | ||
|
|
93d139f926 | ||
|
|
4d1f40537c | ||
|
|
896f1415f7 | ||
|
|
8a01cd9cb0 | ||
|
|
c2013d47e7 | ||
|
|
0f20807690 | ||
|
|
2a45be6a45 | ||
|
|
b63602143e | ||
|
|
2add712270 | ||
|
|
77c8f74bcb | ||
|
|
1d38102541 | ||
|
|
6117b5ab64 | ||
|
|
578f0a110e | ||
|
|
b5c66274ca | ||
|
|
be46bdc57f | ||
|
|
6263e63a1d | ||
|
|
83d702f12b | ||
|
|
37708a0b59 | ||
|
|
6cfcab8156 | ||
|
|
67bcae3c5b | ||
|
|
da65f39d28 | ||
|
|
929854fd55 | ||
|
|
9e216bb3ad | ||
|
|
4bfee3c135 | ||
|
|
6187b19e91 | ||
|
|
54df4a7f8c | ||
|
|
33ec18986d | ||
|
|
6b7b34ae79 | ||
|
|
06ef2482ba | ||
|
|
83bd485597 | ||
|
|
6921f2fe68 | ||
|
|
a7e199932c | ||
|
|
2427c14b7b | ||
|
|
66655d449e | ||
|
|
845a84c4c8 | ||
|
|
0ff5ea3d59 | ||
|
|
09492e647e | ||
|
|
2d6594cc73 | ||
|
|
3b8e83d88a | ||
|
|
3ec4dbb5b8 | ||
|
|
601fdbba39 | ||
|
|
830adc4ef3 | ||
|
|
3f216195ca | ||
|
|
bcb8de0937 | ||
|
|
9cc0d6175e | ||
|
|
4c07632ee6 | ||
|
|
767c219af8 | ||
|
|
cfc301570e | ||
|
|
fb718f9f37 | ||
|
|
cafc0e5628 | ||
|
|
a635725839 | ||
|
|
eb8e9b10ef | ||
|
|
2584f7129c | ||
|
|
771f75788a | ||
|
|
89bc11e4ad | ||
|
|
b8880e8cb8 | ||
|
|
6c996037d6 | ||
|
|
f421b45222 | ||
|
|
30cf46a158 | ||
|
|
8d37c54f89 | ||
|
|
f9b9f39418 | ||
|
|
05a5df789b | ||
|
|
bed87eda97 | ||
|
|
67008c35b8 | ||
|
|
9b95e40a13 | ||
|
|
e3a998b6f8 | ||
|
|
3c7ede0155 | ||
|
|
82c7aa1347 | ||
|
|
4ccd912363 | ||
|
|
c0525842fd | ||
|
|
dd5ca5d233 | ||
|
|
616a4b04a0 | ||
|
|
9a34640c88 | ||
|
|
3110be4703 | ||
|
|
971c8d41ff | ||
|
|
7c303a5154 | ||
|
|
9383b37a41 | ||
|
|
205d507097 | ||
|
|
97a1bd66d1 | ||
|
|
0d1ff6390c | ||
|
|
830d2c87d2 | ||
|
|
da98e7b886 | ||
|
|
a74a9ba5a1 | ||
|
|
b8a3081488 | ||
|
|
c8fc1b0f68 | ||
|
|
da1ddb874f | ||
|
|
771f9ceaa8 | ||
|
|
9b61da5c62 | ||
|
|
7daf7540b3 | ||
|
|
ce6940629d | ||
|
|
b706b96d32 | ||
|
|
2427bc72f2 | ||
|
|
8bc1a9d9ee | ||
|
|
618107fc63 | ||
|
|
0879cb602d | ||
|
|
d5198edfc0 | ||
|
|
647c796497 | ||
|
|
39bba3623a | ||
|
|
8687aeb9bf | ||
|
|
62dac2193f | ||
|
|
22feaf6c77 | ||
|
|
72da0734c8 | ||
|
|
e72f42bda2 | ||
|
|
69b4b76501 | ||
|
|
ae56e27e5f | ||
|
|
12d0a9ec78 | ||
|
|
d6bb6a891d | ||
|
|
ba30225ba1 | ||
|
|
cb4a6e8774 | ||
|
|
2f83b0baa7 | ||
|
|
23f640028d | ||
|
|
04728690ca | ||
|
|
3e5e6263fe | ||
|
|
48cef3e188 | ||
|
|
8c0d6b2f6b | ||
|
|
22ee8acd0d | ||
|
|
afdcfb7bf6 | ||
|
|
c1f6d06128 | ||
|
|
120a8a4576 | ||
|
|
89224e871c | ||
|
|
fa0d472fb6 | ||
|
|
b0f4ad7e4f | ||
|
|
7503d86c69 | ||
|
|
efd1c17033 | ||
|
|
c7a0072f2d | ||
|
|
908942ec09 | ||
|
|
e4d3b53349 | ||
|
|
2a31d740d5 | ||
|
|
03ce5458b5 | ||
|
|
61c03ee206 | ||
|
|
0e4f5b8b2a | ||
|
|
b5d4944ad8 | ||
|
|
50f84d40e1 | ||
|
|
a394d6b37e | ||
|
|
f8408908b2 | ||
|
|
eb8519dc1d | ||
|
|
36dd97394f | ||
|
|
03d4e4dcd1 | ||
|
|
6489a8666f | ||
|
|
5ea64ed805 | ||
|
|
d740446ccb | ||
|
|
d0a2bb7da0 | ||
|
|
5de4ef5d83 | ||
|
|
f59bdf9030 | ||
|
|
cfe0727447 | ||
|
|
09d112350a | ||
|
|
52f8eabd2b | ||
|
|
a162b275a3 | ||
|
|
2e7232bb65 | ||
|
|
82dc9e1c56 | ||
|
|
272a3f579a | ||
|
|
ff1ceb20cb | ||
|
|
343179d4fe | ||
|
|
eabbc2211b | ||
|
|
7f587680ca | ||
|
|
d61ed03ef1 | ||
|
|
166efdc877 | ||
|
|
9274742520 | ||
|
|
9b4c85c9e3 | ||
|
|
66c64ce9e0 | ||
|
|
c9cbffdec8 | ||
|
|
ca3145ce0f | ||
|
|
52f9106776 | ||
|
|
1a2cc5623d | ||
|
|
d06037df1f | ||
|
|
cb8632641e | ||
|
|
adbfcddd9d | ||
|
|
e3aea55e91 | ||
|
|
bf6b1c202f | ||
|
|
0cab47f984 | ||
|
|
829e611692 | ||
|
|
e0e62a52be | ||
|
|
23c0f8e383 | ||
|
|
764a89e5bf | ||
|
|
f4908ed265 | ||
|
|
fcd9c19f0b | ||
|
|
430823dde0 | ||
|
|
7e919a7221 | ||
|
|
7e7f055c34 | ||
|
|
6e04ed7c83 | ||
|
|
5ec07f9620 | ||
|
|
d3da4a8a1e | ||
|
|
9ee10dc5f8 | ||
|
|
572963d987 | ||
|
|
636be8989e | ||
|
|
dea7fd71ef | ||
|
|
be08ed8551 | ||
|
|
65ce588287 | ||
|
|
0cc367b25e | ||
|
|
2ce8549502 | ||
|
|
7245d4eab2 | ||
|
|
260607cb72 | ||
|
|
810738539b | ||
|
|
80539949fb | ||
|
|
ebe5c5b113 | ||
|
|
525182c2a7 | ||
|
|
3704c0cb12 | ||
|
|
c8c844cfba | ||
|
|
4c4e16b0c9 | ||
|
|
69f727c4e5 | ||
|
|
04cff4acb1 | ||
|
|
4e4fcc3ae4 | ||
|
|
485f9d6025 | ||
|
|
a697ade93a | ||
|
|
7db07b5a94 | ||
|
|
9ec50875a2 | ||
|
|
02b6875eec | ||
|
|
e7e4c534bc | ||
|
|
e438fa1d99 | ||
|
|
4abbf50a46 | ||
|
|
3e9279d89a | ||
|
|
1305277c09 | ||
|
|
3c47c672d4 | ||
|
|
258d99cd41 | ||
|
|
83356fa4ef | ||
|
|
25429e78f8 | ||
|
|
ff7523ecb8 | ||
|
|
b53fdadaee | ||
|
|
93390bef63 | ||
|
|
485e2ef4f1 | ||
|
|
9de3b5efb6 | ||
|
|
2632282bba | ||
|
|
7379f50792 | ||
|
|
9ae53d090f | ||
|
|
de90bd1bb0 | ||
|
|
aa6cb4c1d2 | ||
|
|
e871ba600f | ||
|
|
fe3698980d | ||
|
|
677c7bb4cc | ||
|
|
89b640f71c | ||
|
|
d13a9cd04a | ||
|
|
c0dab92d0e | ||
|
|
4fdc241b9c | ||
|
|
06ca7603b9 | ||
|
|
28dc4867db | ||
|
|
1cf13da413 | ||
|
|
9c897972ad | ||
|
|
307e244475 | ||
|
|
9d3aca646b | ||
|
|
e6e61466df | ||
|
|
db7f9fe2ab | ||
|
|
ded798fdf1 | ||
|
|
bfe94e3068 | ||
|
|
823f07409a | ||
|
|
1a4bc720c2 | ||
|
|
73cacdec24 | ||
|
|
18998c4dbe | ||
|
|
e236d6e912 | ||
|
|
523df670df | ||
|
|
ce58181fc3 | ||
|
|
bed0669f73 | ||
|
|
b0d077e104 | ||
|
|
bdb2951330 | ||
|
|
87a01208fb | ||
|
|
2daee84fbf | ||
|
|
cdbf58f3ac | ||
|
|
f6a59bdf55 | ||
|
|
e12edd977e | ||
|
|
ea72d44b42 | ||
|
|
b925c991eb | ||
|
|
1e7f43fe3d | ||
|
|
d1b9b5546b | ||
|
|
6f21de1901 | ||
|
|
25e8eaa1d4 | ||
|
|
84f0affaed | ||
|
|
6fe736ce06 | ||
|
|
482b03c2d1 | ||
|
|
ce3c72fc47 | ||
|
|
1ff5ed4141 | ||
|
|
fc4b5c6b1d | ||
|
|
10e3421572 | ||
|
|
0117237988 | ||
|
|
2c232a71d5 | ||
|
|
373fd817d0 | ||
|
|
0ef2d9646d | ||
|
|
c8ac417200 | ||
|
|
661bedbe5b | ||
|
|
2dd56590d3 | ||
|
|
98b760251c | ||
|
|
b97de32a44 | ||
|
|
3919efaa77 | ||
|
|
a76fc780cb | ||
|
|
d915c123db | ||
|
|
92c8b54f85 | ||
|
|
d8420f472c | ||
|
|
34d93c4de0 | ||
|
|
1c400cd456 | ||
|
|
f8e1758788 | ||
|
|
5c95c72f40 | ||
|
|
98f816b069 | ||
|
|
3ca6308dd2 | ||
|
|
a2c2aa11ac | ||
|
|
a3cf97fcab | ||
|
|
1a9dc7a377 | ||
|
|
806daebd3f | ||
|
|
dfd8845864 | ||
|
|
b5b772d0c2 | ||
|
|
170108b339 | ||
|
|
9f1f58a9c7 | ||
|
|
4d8a2e635c | ||
|
|
0852d55837 | ||
|
|
4c38ddf3cd | ||
|
|
e15edeadb5 | ||
|
|
422c7baada | ||
|
|
4288e2d986 | ||
|
|
4b289388bf | ||
|
|
2a2f8e51b3 | ||
|
|
ed0136090c | ||
|
|
0d70545b98 | ||
|
|
2252091b53 | ||
|
|
85b1875a22 | ||
|
|
386531884b | ||
|
|
afbe328aa7 | ||
|
|
b7c0fba48b | ||
|
|
68635b1629 | ||
|
|
3bd0058dc8 | ||
|
|
270a512585 | ||
|
|
ec2519eae4 | ||
|
|
d7f52d864a | ||
|
|
5cb2b3940e | ||
|
|
b5efaa944d | ||
|
|
bbcfc420d2 | ||
|
|
8ebf7baa71 | ||
|
|
742d2b5ff2 | ||
|
|
f96fefbfdc | ||
|
|
6570d38719 | ||
|
|
547b58f05a | ||
|
|
f299c685e2 | ||
|
|
59075ee610 | ||
|
|
432aa9f1e1 | ||
|
|
c5bed4f36d | ||
|
|
151c9c0add | ||
|
|
334306e3c9 | ||
|
|
0716920dfc | ||
|
|
07119e4e7e | ||
|
|
2a7606836c | ||
|
|
7dcdd64a17 | ||
|
|
f26b045727 | ||
|
|
79c966f9e4 | ||
|
|
8eff1dfc4c | ||
|
|
65f960db00 | ||
|
|
f14dc497cf | ||
|
|
89339e1033 | ||
|
|
74da3ec1ca | ||
|
|
7e99a51495 | ||
|
|
146a61c9cf | ||
|
|
5250d97935 | ||
|
|
18dce9795d | ||
|
|
8d170a5bb4 | ||
|
|
7add2bb12e | ||
|
|
8670f386dc | ||
|
|
1f62108e57 | ||
|
|
8715ef4f24 | ||
|
|
85b137f0d6 | ||
|
|
14ebb280a3 | ||
|
|
f1953eef29 | ||
|
|
55842faedd | ||
|
|
9c50de85de | ||
|
|
e6df079431 | ||
|
|
36ce547579 | ||
|
|
ae5fef435a | ||
|
|
fe55701079 | ||
|
|
87b567e990 | ||
|
|
c2b3905c8e | ||
|
|
32072f1d6c | ||
|
|
e84d3bf53a | ||
|
|
67a7e4b865 | ||
|
|
3d7da0b28a | ||
|
|
9320587595 | ||
|
|
7bceba7ed5 | ||
|
|
0db72cd9e4 | ||
|
|
53fc5e361f | ||
|
|
54af163ddf | ||
|
|
c0d756fa38 | ||
|
|
54e673176c | ||
|
|
fc16190ec4 | ||
|
|
cf7f4f1b46 | ||
|
|
756fc07193 | ||
|
|
189c60a6d1 | ||
|
|
ba95a636cf | ||
|
|
2478fedf1a | ||
|
|
a718f2a012 | ||
|
|
bc6c889afc | ||
|
|
33bcf250d0 | ||
|
|
bad32e069b | ||
|
|
9acf20d4f3 | ||
|
|
26f1ee0d89 | ||
|
|
9c86be8250 | ||
|
|
4a06f9a686 | ||
|
|
98700f54b4 | ||
|
|
9a37cb5cb8 | ||
|
|
673d0bb7c5 | ||
|
|
04c7bc445b | ||
|
|
41782fe120 | ||
|
|
71a5722b0e | ||
|
|
4507135a1b | ||
|
|
4e8e25a336 | ||
|
|
3c2d3156cb | ||
|
|
0dac15391f | ||
|
|
106534b59b | ||
|
|
19c0553746 | ||
|
|
5f1475d2ec | ||
|
|
84f4d5956a | ||
|
|
4330ddd926 | ||
|
|
0711210512 | ||
|
|
458ec76835 | ||
|
|
682ea860fb | ||
|
|
99977934e7 | ||
|
|
3e05b21c90 | ||
|
|
4e1dd52bea | ||
|
|
f6bcc743d8 | ||
|
|
7f2479f597 | ||
|
|
66b97be9d2 | ||
|
|
7472285641 | ||
|
|
d747594e39 | ||
|
|
67ff9f30c6 | ||
|
|
a27092dbcc | ||
|
|
ca41bff446 | ||
|
|
cf8280590c | ||
|
|
b649ca1f00 | ||
|
|
b441301007 | ||
|
|
2e93238b5c | ||
|
|
ce4fe84536 | ||
|
|
9b7c0af025 | ||
|
|
dfdaf36ed1 | ||
|
|
57909ebc61 | ||
|
|
cc8d1b3793 | ||
|
|
a33bfedbb8 | ||
|
|
d1090670a8 | ||
|
|
f4bcad1b06 | ||
|
|
eb359d83c5 | ||
|
|
ae13e9e36a | ||
|
|
4a62ac2a11 | ||
|
|
6656708c8e | ||
|
|
bfe3fb2ed0 | ||
|
|
ec21521281 | ||
|
|
016a62b6d5 | ||
|
|
3e226b50ab | ||
|
|
ab84cb5ada | ||
|
|
7825aa4122 | ||
|
|
e71e671d7d | ||
|
|
8d43fbfcd9 | ||
|
|
47a01628d3 | ||
|
|
c008660023 | ||
|
|
5da34cbeac | ||
|
|
5b29aec14b | ||
|
|
ca6aa682f6 | ||
|
|
eb3786cebf | ||
|
|
53843e22a4 | ||
|
|
e1693674ca | ||
|
|
2d2190e4fa | ||
|
|
02fd8097a8 | ||
|
|
fcfbc85683 | ||
|
|
802dd696f4 | ||
|
|
df6b0fae21 | ||
|
|
69723a8bf4 | ||
|
|
241caa6132 | ||
|
|
13b93f6d9d | ||
|
|
77555366f4 | ||
|
|
f3e8132bfc | ||
|
|
7510385cf4 | ||
|
|
60a0222dd0 | ||
|
|
9114abd3ef | ||
|
|
b3f69d4088 | ||
|
|
8796c8fe06 | ||
|
|
f7fc0e6a6d | ||
|
|
ffebbe3b2a | ||
|
|
25b8c1b1eb | ||
|
|
17f8625108 | ||
|
|
e3c21f0373 | ||
|
|
859ff00277 | ||
|
|
e7c3be5231 | ||
|
|
85e3c5a433 | ||
|
|
d8d5cde3f1 | ||
|
|
6efa08fee3 | ||
|
|
636c13373c | ||
|
|
3659fbec84 | ||
|
|
05f1a9b280 | ||
|
|
34b4baac3d | ||
|
|
5884d5eba0 | ||
|
|
6b4709b76b | ||
|
|
4dd2137006 | ||
|
|
03315836a6 | ||
|
|
f703ba2cf9 | ||
|
|
4513acc640 | ||
|
|
612e359d4c | ||
|
|
c8fb1ce302 | ||
|
|
b29d8e1912 | ||
|
|
f1aa7944a3 | ||
|
|
74b6c2b6b5 | ||
|
|
4313cf471f | ||
|
|
fce8039dad | ||
|
|
085ae141ae | ||
|
|
fbc9ccc018 | ||
|
|
f2ede519d7 | ||
|
|
21c53473d3 | ||
|
|
fbc622fa04 | ||
|
|
6319fd20fa | ||
|
|
c998e4901f | ||
|
|
06c35a4ff8 | ||
|
|
171c0b2b5a | ||
|
|
fd4820336f | ||
|
|
46840266ee | ||
|
|
82a0d287d6 | ||
|
|
4de604ef7c | ||
|
|
ec2c26ca69 | ||
|
|
026ce853e2 | ||
|
|
720b7f891b | ||
|
|
b5332458ec | ||
|
|
3c7b16412b | ||
|
|
a454c57bc9 | ||
|
|
86ff7cbc69 | ||
|
|
fbfdbc903c | ||
|
|
9e64fdc985 | ||
|
|
d7c23297ab | ||
|
|
f6e095e0a6 | ||
|
|
d7ec2e717c | ||
|
|
35fd74d3fe | ||
|
|
8325e2d9cf | ||
|
|
cc3c1242f5 | ||
|
|
b2f4a5539c |
@@ -8,13 +8,13 @@ orbs:
|
|||||||
jobs:
|
jobs:
|
||||||
api-deploy:
|
api-deploy:
|
||||||
docker:
|
docker:
|
||||||
- image: "cimg/base:stable"
|
- image: cimg/node:18.18.2
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- eb/setup
|
- eb/setup
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
eb init imex-online-production-api -r ca-central-1 -p "Node.js 16 running on 64bit Amazon Linux 2"
|
eb init imex-online-production-api -r ca-central-1 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
@@ -42,31 +42,22 @@ jobs:
|
|||||||
app-build:
|
app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:16.15.0
|
- image: cimg/node:16.15.0
|
||||||
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build
|
- run: npm run build
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-production/"
|
to: "s3://imex-online-production/"
|
||||||
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify
|
- jira/notify
|
||||||
|
|
||||||
rome-api-deploy:
|
rome-api-deploy:
|
||||||
@@ -77,7 +68,7 @@ jobs:
|
|||||||
- eb/setup
|
- eb/setup
|
||||||
- run:
|
- run:
|
||||||
command: |
|
command: |
|
||||||
eb init romeonline-productionapi -r us-east-2 -p "Node.js 16 running on 64bit Amazon Linux 2"
|
eb init romeonline-productionapi -r us-east-2 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
@@ -105,27 +96,17 @@ jobs:
|
|||||||
rome-app-build:
|
rome-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:16.15.0
|
- image: cimg/node:16.15.0
|
||||||
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build
|
- run: npm run build
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
@@ -155,33 +136,62 @@ jobs:
|
|||||||
test-rome-app-build:
|
test-rome-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:16.15.0
|
- image: cimg/node:16.15.0
|
||||||
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build:test
|
- run: npm run build:test
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://rome-online-test/"
|
to: "s3://rome-online-test/"
|
||||||
- jira/notify
|
- jira/notify
|
||||||
|
|
||||||
|
app-beta-build:
|
||||||
|
docker:
|
||||||
|
- image: cimg/node:18.18.2
|
||||||
|
resource_class: large
|
||||||
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
path: ~/repo
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: npm i
|
||||||
|
|
||||||
|
- run: npm run build
|
||||||
|
|
||||||
|
- aws-s3/sync:
|
||||||
|
from: build
|
||||||
|
to: "s3://imex-online-beta/"
|
||||||
|
- jira/notify
|
||||||
|
|
||||||
|
rome-app-beta-build:
|
||||||
|
docker:
|
||||||
|
- image: cimg/node:18.18.2
|
||||||
|
resource_class: large
|
||||||
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
path: ~/repo
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: npm i
|
||||||
|
|
||||||
|
- run: npm run build
|
||||||
|
|
||||||
|
- aws-s3/sync:
|
||||||
|
from: build
|
||||||
|
to: "s3://rome-online-production-beta/"
|
||||||
|
- jira/notify
|
||||||
|
|
||||||
test-hasura-migrate:
|
test-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
@@ -206,33 +216,67 @@ jobs:
|
|||||||
test-app-build:
|
test-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:16.15.0
|
- image: cimg/node:16.15.0
|
||||||
|
resource_class: large
|
||||||
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
path: ~/repo
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: npm i
|
||||||
|
|
||||||
|
- run: npm run build:test
|
||||||
|
|
||||||
|
- aws-s3/sync:
|
||||||
|
from: build
|
||||||
|
to: "s3://imex-online-test/"
|
||||||
|
arguments: "--exclude '*.map'"
|
||||||
|
- jira/notify
|
||||||
|
|
||||||
|
test-app-beta-build:
|
||||||
|
docker:
|
||||||
|
- image: cimg/node:18.18.2
|
||||||
|
resource_class: large
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build:test
|
- run: npm run build:test
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-test/"
|
to: "s3://imex-online-test-beta/"
|
||||||
- jira/notify
|
- jira/notify
|
||||||
|
|
||||||
|
rome-test-app-beta-build:
|
||||||
|
docker:
|
||||||
|
- image: cimg/node:18.18.2
|
||||||
|
resource_class: large
|
||||||
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
path: ~/repo
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: npm i
|
||||||
|
|
||||||
|
- run: npm run build:test
|
||||||
|
|
||||||
|
- aws-s3/sync:
|
||||||
|
from: build
|
||||||
|
to: "s3://rome-online-test-beta/"
|
||||||
|
- jira/notify
|
||||||
|
|
||||||
|
|
||||||
admin-app-build:
|
admin-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:16.15.0
|
- image: cimg/node:16.15.0
|
||||||
@@ -274,6 +318,14 @@ workflows:
|
|||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: master
|
only: master
|
||||||
|
- app-beta-build:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: master-beta
|
||||||
|
- rome-app-beta-build:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: rome/master-beta
|
||||||
- hasura-migrate:
|
- hasura-migrate:
|
||||||
secret: ${HASURA_PROD_SECRET}
|
secret: ${HASURA_PROD_SECRET}
|
||||||
filters:
|
filters:
|
||||||
@@ -296,6 +348,14 @@ workflows:
|
|||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: test
|
only: test
|
||||||
|
- rome-test-app-beta-build:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: rome/test-beta
|
||||||
|
- test-app-beta-build:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: test-beta
|
||||||
- test-hasura-migrate:
|
- test-hasura-migrate:
|
||||||
secret: ${HASURA_TEST_SECRET}
|
secret: ${HASURA_TEST_SECRET}
|
||||||
filters:
|
filters:
|
||||||
@@ -313,4 +373,4 @@ workflows:
|
|||||||
#- admin-app-build:
|
#- admin-app-build:
|
||||||
#filters:
|
#filters:
|
||||||
#branches:
|
#branches:
|
||||||
#only: master
|
#only: master
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -117,4 +117,6 @@ logs/oAuthClient-log.log
|
|||||||
|
|
||||||
.node-persist/**
|
.node-persist/**
|
||||||
|
|
||||||
/*.env.*
|
/*.env.*
|
||||||
|
.idea/*
|
||||||
|
.idea
|
||||||
16
.prettierrc.js
Normal file
16
.prettierrc.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
exports.default = {
|
||||||
|
printWidth: 120,
|
||||||
|
useTabs: false,
|
||||||
|
tabWidth: 2,
|
||||||
|
trailingComma: "es5",
|
||||||
|
semi: true,
|
||||||
|
singleQuote: false,
|
||||||
|
bracketSpacing: true,
|
||||||
|
arrowParens: "always",
|
||||||
|
jsxSingleQuote: false,
|
||||||
|
bracketSameLine: false,
|
||||||
|
endOfLine: "lf",
|
||||||
|
importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
|
||||||
|
importOrderSeparation: true,
|
||||||
|
importOrderSortSpecifiers: true,
|
||||||
|
};
|
||||||
184
_reference/reportFiltersAndSorters.md
Normal file
184
_reference/reportFiltersAndSorters.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# Filters and Sorters
|
||||||
|
|
||||||
|
This documentation details the schema required for `.filters` files on the report server. It is used to dynamically
|
||||||
|
modify the graphQL query and provide the user more power over their reports.
|
||||||
|
|
||||||
|
For filters and sorters, valid types include (`type` key in the schema):
|
||||||
|
- string (default)
|
||||||
|
- number
|
||||||
|
- bool or boolean
|
||||||
|
- date
|
||||||
|
|
||||||
|
## Special Notes
|
||||||
|
- When passing the data to the template server, the property filters and sorters is added to the data object and will reflect the filters and sorters the user has selected
|
||||||
|
|
||||||
|
## High level Schema Overview
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const schema = {
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"name": "jobs.joblines.mod_lb_hrs", // Name and path of the field in the graphQL query
|
||||||
|
"translation": "jobs.joblines.mod_lb_hrs_1", // Translation key for the label used in the GUI
|
||||||
|
"label": "mod_lb_hrs_1", // Label used in the case the GUI does not contain a translation
|
||||||
|
"type": "number" // Type of field, can be number or string currently
|
||||||
|
},
|
||||||
|
// ... more filters
|
||||||
|
],
|
||||||
|
"sorters": [
|
||||||
|
{
|
||||||
|
"name": "jobs.joblines.mod_lb_hrs", // Name and path of the field in the graphQL query
|
||||||
|
"translation": "jobs.joblines.mod_lb_hrs_1", // Translation key for the label used in the GUI
|
||||||
|
"label": "mod_lb_hrs_1", // Label used in the case the GUI does not contain a translation
|
||||||
|
"type": "number" // Type of field, can be number or string currently
|
||||||
|
},
|
||||||
|
// ... more sorters
|
||||||
|
],
|
||||||
|
"dates": {
|
||||||
|
// This is not yet implemented and will be added in a future release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Filters
|
||||||
|
|
||||||
|
Filters effect the where clause of the graphQL query. They are used to filter the data returned from the server.
|
||||||
|
A note on special notation used in the `name` field.
|
||||||
|
|
||||||
|
## Reflection
|
||||||
|
|
||||||
|
Filters can make use of reflection to pre-fill select boxes, the following is an example of that in the filters file.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "jobs.status",
|
||||||
|
"translation": "jobs.fields.status",
|
||||||
|
"label": "Status",
|
||||||
|
"type": "string",
|
||||||
|
"reflector": {
|
||||||
|
"type": "internal",
|
||||||
|
"name": "special.job_statuses"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
in this example, a reflector with the type 'internal' (all types at the moment require this, and it is used for future functionality), with a name of `special.job_statuses`
|
||||||
|
|
||||||
|
The following cases are available
|
||||||
|
|
||||||
|
- `special.job_statuses` - This will reflect the statuses of the jobs table `bodyshop.md_ro_statuses.statuses'`
|
||||||
|
- `special.cost_centers` - This will reflect the cost centers `bodyshop.md_responsibility_centers.costs`
|
||||||
|
- `special.categories` - This will reflect the categories `bodyshop.md_categories`
|
||||||
|
- `special.insurance_companies` - This will reflect the insurance companies `bodyshop.md_ins_cos`'
|
||||||
|
- `special.employee_teams` - This will reflect the employee teams `bodyshop.employee_teams`
|
||||||
|
- `special.employees` - This will reflect the employees `bodyshop.employees`
|
||||||
|
- `special.first_names` - This will reflect the first names `bodyshop.employees`
|
||||||
|
- `special.last_names` - This will reflect the last names `bodyshop.employees`
|
||||||
|
- `special.referral_sources` - This will reflect the referral sources `bodyshop.md_referral_sources
|
||||||
|
- `special.class`- This will reflect the class `bodyshop.md_classes`
|
||||||
|
-
|
||||||
|
### Path without brackets, multi level
|
||||||
|
|
||||||
|
`"name": "jobs.joblines.mod_lb_hrs",`
|
||||||
|
This will produce a where clause at the `joblines` level of the graphQL query,
|
||||||
|
|
||||||
|
```graphql
|
||||||
|
query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!) {
|
||||||
|
jobs(
|
||||||
|
where: {date_invoiced: {_is_null: true}, date_open: {_gte: $starttz, _lte: $endtz}, ro_number: {_is_null: false}, voided: {_eq: false}}
|
||||||
|
) {
|
||||||
|
joblines(
|
||||||
|
order_by: {line_no: asc}
|
||||||
|
where: {removed: {_eq: false}, mod_lb_hrs: {_lt: 3}}
|
||||||
|
) {
|
||||||
|
line_no
|
||||||
|
mod_lbr_ty
|
||||||
|
mod_lb_hrs
|
||||||
|
convertedtolbr
|
||||||
|
convertedtolbr_data
|
||||||
|
}
|
||||||
|
ownr_co_nm
|
||||||
|
ownr_fn
|
||||||
|
ownr_ln
|
||||||
|
plate_no
|
||||||
|
ro_number
|
||||||
|
status
|
||||||
|
v_make_desc
|
||||||
|
v_model_desc
|
||||||
|
v_model_yr
|
||||||
|
v_vin
|
||||||
|
v_color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Path with brackets,top level
|
||||||
|
|
||||||
|
`"name": "[jobs].joblines.mod_lb_hrs",`
|
||||||
|
This will produce a where clause at the `jobs` level of the graphQL query.
|
||||||
|
|
||||||
|
```graphql
|
||||||
|
query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!) {
|
||||||
|
jobs(
|
||||||
|
where: {date_invoiced: {_is_null: true}, date_open: {_gte: $starttz, _lte: $endtz}, ro_number: {_is_null: false}, voided: {_eq: false}, joblines: {mod_lb_hrs: {_gt: 4}}}
|
||||||
|
) {
|
||||||
|
joblines(
|
||||||
|
order_by: {line_no: asc}
|
||||||
|
where: {removed: {_eq: false}}
|
||||||
|
) {
|
||||||
|
line_no
|
||||||
|
mod_lbr_ty
|
||||||
|
mod_lb_hrs
|
||||||
|
convertedtolbr
|
||||||
|
convertedtolbr_data
|
||||||
|
}
|
||||||
|
ownr_co_nm
|
||||||
|
ownr_fn
|
||||||
|
ownr_ln
|
||||||
|
plate_no
|
||||||
|
ro_number
|
||||||
|
status
|
||||||
|
v_make_desc
|
||||||
|
v_model_desc
|
||||||
|
v_model_yr
|
||||||
|
v_vin
|
||||||
|
v_color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Known Caveats
|
||||||
|
|
||||||
|
- Will only support two level of nesting in the graphQL query `jobs.joblines.mod_lb_hrs` vs `[jobs].joblines.mod_lb_hrs`
|
||||||
|
is fine, but `jobs.[joblines.].some_table.mod_lb_hrs` is not.
|
||||||
|
- The type object must be 'string' or 'number' or 'bool' or 'boolean' or 'date' and is case-sensitive.
|
||||||
|
- The `translation` key is used to look up the label in the GUI, if it is not found, the `label` key is used.
|
||||||
|
- Do not add the ability to filter things that are already filtered as part of the original query, this would be
|
||||||
|
redundant and could cause issues.
|
||||||
|
- Do not add the ability to filter on things like FK constraints, must like the above example.
|
||||||
|
|
||||||
|
## Sorters
|
||||||
|
|
||||||
|
- Sorters follow the same schema as filters, however, they do not do square bracket wrapping to indicate level hoisting,
|
||||||
|
a filter added on `job.md_status` would be added at the top level, and a filter added on `jobs.joblines.mod_lb_hrs`
|
||||||
|
would be added at the `joblines` level.
|
||||||
|
- Most of the reports currently do sorting on a template level, this will need to change to actually see the results
|
||||||
|
using the sorters.
|
||||||
|
|
||||||
|
### Default Sorters
|
||||||
|
|
||||||
|
- A sorter can be given a default object containing a `order` and `direction` key value. This will be used to sort the report if the user does not select any of the sorters themselves.
|
||||||
|
- The `order` key is the order in which the sorters are applied, and the `direction` key is the direction of the sort, either `asc` or `desc`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "jobs.joblines.mod_lb_hrs",
|
||||||
|
"translation": "jobs.joblines.mod_lb_hrs_1",
|
||||||
|
"label": "mod_lb_hrs_1",
|
||||||
|
"type": "number",
|
||||||
|
"default": {
|
||||||
|
"order": 1,
|
||||||
|
"direction": "asc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
File diff suppressed because it is too large
Load Diff
3
client/.gitignore
vendored
Normal file
3
client/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
# Sentry Config File
|
||||||
|
.sentryclirc
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
// craco.config.js
|
// craco.config.js
|
||||||
const TerserPlugin = require("terser-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
const CracoLessPlugin = require("craco-less");
|
const CracoLessPlugin = require("craco-less");
|
||||||
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
//const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
// {
|
||||||
plugin: SentryWebpackPlugin,
|
// plugin: SentryWebpackPlugin,
|
||||||
options: {
|
// options: {
|
||||||
// sentry-cli configuration
|
// // sentry-cli configuration
|
||||||
authToken:
|
// authToken:
|
||||||
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
// "6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
||||||
org: "snapt-software",
|
// org: "snapt-software",
|
||||||
project: "rome-online",
|
// project: "imexonline",
|
||||||
release: process.env.REACT_APP_GIT_SHA,
|
// release: process.env.REACT_APP_GIT_SHA,
|
||||||
|
|
||||||
// webpack-specific configuration
|
// // webpack-specific configuration
|
||||||
include: ".",
|
// include: ".",
|
||||||
ignore: ["node_modules", "webpack.config.js"],
|
// ignore: ["node_modules", "webpack.config.js"],
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
plugin: CracoLessPlugin,
|
plugin: CracoLessPlugin,
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
24741
client/package-lock.json
generated
Normal file
24741
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,14 +7,16 @@
|
|||||||
"@apollo/client": "^3.7.9",
|
"@apollo/client": "^3.7.9",
|
||||||
"@asseinfo/react-kanban": "^2.2.0",
|
"@asseinfo/react-kanban": "^2.2.0",
|
||||||
"@craco/craco": "^7.0.0",
|
"@craco/craco": "^7.0.0",
|
||||||
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@sentry/react": "^7.40.0",
|
"@sentry/cli": "^2.27.0",
|
||||||
|
"@sentry/react": "^7.99.0",
|
||||||
"@sentry/tracing": "^7.40.0",
|
"@sentry/tracing": "^7.40.0",
|
||||||
"@splitsoftware/splitio-react": "^1.8.1",
|
"@splitsoftware/splitio-react": "^1.8.1",
|
||||||
"@tanem/react-nprogress": "^5.0.8",
|
"@tanem/react-nprogress": "^5.0.8",
|
||||||
"antd": "^4.24.8",
|
"antd": "^4.24.8",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
|
"apollo-link-sentry": "^3.3.0",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"craco-less": "^2.0.0",
|
"craco-less": "^2.0.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
@@ -88,13 +90,14 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||||
"start": "craco start",
|
"start": "craco start",
|
||||||
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build && npm run sentry:sourcemaps",
|
||||||
"build:test": "env-cmd -f .env.test yarn run build",
|
"build:test": "env-cmd -f .env.test npm run build",
|
||||||
"build-deploy:test": "yarn run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
|
"build-deploy:test": "npm run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
|
||||||
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
||||||
"test": "cypress open",
|
"test": "cypress open",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular ."
|
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
||||||
|
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
@@ -7,6 +7,75 @@
|
|||||||
<meta name="theme-color" content="#002366" />
|
<meta name="theme-color" content="#002366" />
|
||||||
<meta name="description" content="Rome Online" />
|
<meta name="description" content="Rome Online" />
|
||||||
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
||||||
|
|
||||||
|
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
|
||||||
|
|
||||||
|
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001 party="LiveChat528346"></call-us-selector>
|
||||||
|
|
||||||
|
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
|
||||||
|
|
||||||
|
<!--<call-us
|
||||||
|
|
||||||
|
phonesystem-url=https://rometech.east.3cx.us:5001
|
||||||
|
|
||||||
|
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
|
||||||
|
|
||||||
|
id="wp-live-chat-by-3CX"
|
||||||
|
|
||||||
|
minimized="true"
|
||||||
|
|
||||||
|
animation-style="noanimation"
|
||||||
|
|
||||||
|
party="LiveChat528346"
|
||||||
|
|
||||||
|
minimized-style="bubbleright"
|
||||||
|
|
||||||
|
allow-call="true"
|
||||||
|
|
||||||
|
allow-video="false"
|
||||||
|
|
||||||
|
allow-soundnotifications="true"
|
||||||
|
|
||||||
|
enable-mute="true"
|
||||||
|
|
||||||
|
enable-onmobile="true"
|
||||||
|
|
||||||
|
offline-enabled="true"
|
||||||
|
|
||||||
|
enable="true"
|
||||||
|
|
||||||
|
ignore-queueownership="false"
|
||||||
|
|
||||||
|
authentication="both"
|
||||||
|
|
||||||
|
show-operator-actual-name="true"
|
||||||
|
|
||||||
|
aknowledge-received="true"
|
||||||
|
|
||||||
|
gdpr-enabled="false"
|
||||||
|
|
||||||
|
message-userinfo-format="name"
|
||||||
|
|
||||||
|
message-dateformat="both"
|
||||||
|
|
||||||
|
lang="browser"
|
||||||
|
|
||||||
|
button-icon-type="default"
|
||||||
|
|
||||||
|
greeting-visibility="none"
|
||||||
|
|
||||||
|
greeting-offline-visibility="none"
|
||||||
|
|
||||||
|
chat-delay="2000"
|
||||||
|
|
||||||
|
enable-direct-call="true"
|
||||||
|
|
||||||
|
enable-ga="false"
|
||||||
|
|
||||||
|
></call-us>-->
|
||||||
|
|
||||||
|
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js id="tcx-callus-js" charset="utf-8"></script>
|
||||||
|
|
||||||
<link rel="apple-touch-icon" href="logo192.png" />
|
<link rel="apple-touch-icon" href="logo192.png" />
|
||||||
<script>
|
<script>
|
||||||
!(function () {
|
!(function () {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||||
import client from "../utils/GraphQLClient";
|
import client from "../utils/GraphQLClient";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
moment.locale("en-US");
|
moment.locale("en-US");
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ export const factory = SplitSdk({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function AppContainer() {
|
function AppContainer() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -48,3 +49,5 @@ export default function AppContainer() {
|
|||||||
</ApolloProvider>
|
</ApolloProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Sentry.withProfiler(AppContainer);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
} from "../redux/user/user.selectors";
|
} from "../redux/user/user.selectors";
|
||||||
import PrivateRoute from "../utils/private-route";
|
import PrivateRoute from "../utils/private-route";
|
||||||
import "./App.styles.scss";
|
import "./App.styles.scss";
|
||||||
|
import handleBeta from "../utils/handleBeta";
|
||||||
|
|
||||||
const ResetPassword = lazy(() =>
|
const ResetPassword = lazy(() =>
|
||||||
import("../pages/reset-password/reset-password.component")
|
import("../pages/reset-password/reset-password.component")
|
||||||
@@ -57,7 +58,6 @@ export function App({
|
|||||||
if (!navigator.onLine) {
|
if (!navigator.onLine) {
|
||||||
setOnline(false);
|
setOnline(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkUserSession();
|
checkUserSession();
|
||||||
}, [checkUserSession, setOnline]);
|
}, [checkUserSession, setOnline]);
|
||||||
|
|
||||||
@@ -73,6 +73,7 @@ export function App({
|
|||||||
window.addEventListener("online", function (e) {
|
window.addEventListener("online", function (e) {
|
||||||
setOnline(true);
|
setOnline(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentUser.authorized && bodyshop) {
|
if (currentUser.authorized && bodyshop) {
|
||||||
client.setAttribute("imexshopid", bodyshop.imexshopid);
|
client.setAttribute("imexshopid", bodyshop.imexshopid);
|
||||||
@@ -107,6 +108,8 @@ export function App({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
handleBeta();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Suspense fallback={<LoadingSpinner />}>
|
<Suspense fallback={<LoadingSpinner />}>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -210,7 +211,7 @@ export function AccountingPayablesTableComponent({
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ position: "top", pageSize: 50 }}
|
pagination={{ position: "top", pageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import PaymentExportButton from "../payment-export-button/payment-export-button.
|
|||||||
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
|
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
|
||||||
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -209,7 +210,7 @@ export function AccountingPayablesTableComponent({
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ position: "top", pageSize: 50 }}
|
pagination={{ position: "top", pageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { alphaSort } from "../../utils/sorters";
|
|||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component";
|
import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
|
|
||||||
export default function AuditTrailListComponent({ loading, data }) {
|
export default function AuditTrailListComponent({ loading, data }) {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
@@ -74,7 +75,7 @@ export default function AuditTrailListComponent({ loading, data }) {
|
|||||||
<Table
|
<Table
|
||||||
{...formItemLayout}
|
{...formItemLayout}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
|
|
||||||
export default function EmailAuditTrailListComponent({ loading, data }) {
|
export default function EmailAuditTrailListComponent({ loading, data }) {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
@@ -53,7 +54,7 @@ export default function EmailAuditTrailListComponent({ loading, data }) {
|
|||||||
<Table
|
<Table
|
||||||
{...formItemLayout}
|
{...formItemLayout}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={data}
|
dataSource={data}
|
||||||
|
|||||||
@@ -3,10 +3,22 @@ import { useMutation } from "@apollo/client";
|
|||||||
import { Button, notification, Popconfirm } from "antd";
|
import { Button, notification, Popconfirm } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
import { DELETE_BILL } from "../../graphql/bills.queries";
|
import { DELETE_BILL } from "../../graphql/bills.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
|
|
||||||
export default function BillDeleteButton({ bill, callback }) {
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
insertAuditTrail: ({ jobid, operation }) =>
|
||||||
|
dispatch(insertAuditTrail({ jobid, operation })),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(BillDeleteButton);
|
||||||
|
|
||||||
|
export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [deleteBill] = useMutation(DELETE_BILL);
|
const [deleteBill] = useMutation(DELETE_BILL);
|
||||||
@@ -36,6 +48,10 @@ export default function BillDeleteButton({ bill, callback }) {
|
|||||||
|
|
||||||
if (!!!result.errors) {
|
if (!!!result.errors) {
|
||||||
notification["success"]({ message: t("bills.successes.deleted") });
|
notification["success"]({ message: t("bills.successes.deleted") });
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: jobid,
|
||||||
|
operation: AuditTrailMapping.billdeleted(bill.invoice_number),
|
||||||
|
});
|
||||||
|
|
||||||
if (callback && typeof callback === "function") callback(bill.id);
|
if (callback && typeof callback === "function") callback(bill.id);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import {
|
import {
|
||||||
DELETE_BILL_LINE,
|
DELETE_BILL_LINE,
|
||||||
INSERT_NEW_BILL_LINES,
|
INSERT_NEW_BILL_LINES,
|
||||||
UPDATE_BILL_LINE
|
UPDATE_BILL_LINE,
|
||||||
} from "../../graphql/bill-lines.queries";
|
} from "../../graphql/bill-lines.queries";
|
||||||
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
@@ -20,6 +20,7 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import BillFormContainer from "../bill-form/bill-form.container";
|
import BillFormContainer from "../bill-form/bill-form.container";
|
||||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||||
|
import BillPrintButton from "../bill-print-button/bill-print-button.component";
|
||||||
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
|
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
|
||||||
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
||||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||||
@@ -176,7 +177,7 @@ export function BillDetailEditcontainer({
|
|||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<BillDetailEditReturn data={data} />
|
<BillDetailEditReturn data={data} />
|
||||||
|
<BillPrintButton billid={search.billid} />
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onConfirm={() => form.submit()}
|
onConfirm={() => form.submit()}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useApolloClient, useMutation } from "@apollo/client";
|
import { useApolloClient, useMutation } from "@apollo/client";
|
||||||
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
|
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
@@ -29,7 +30,6 @@ import BillFormContainer from "../bill-form/bill-form.container";
|
|||||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
billEnterModal: selectBillEnterModal,
|
billEnterModal: selectBillEnterModal,
|
||||||
@@ -38,8 +38,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
|
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({ jobid, billid, operation }) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({ jobid, billid, operation })),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Templates = TemplateList("job_special");
|
const Templates = TemplateList("job_special");
|
||||||
@@ -72,6 +72,11 @@ function BillEnterModalContainer({
|
|||||||
const formValues = useMemo(() => {
|
const formValues = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
...billEnterModal.context.bill,
|
...billEnterModal.context.bill,
|
||||||
|
//Added as a part of IO-2436 for capturing parts price changes.
|
||||||
|
billlines: billEnterModal.context?.bill?.billlines?.map((line) => ({
|
||||||
|
...line,
|
||||||
|
original_actual_price: line.actual_price,
|
||||||
|
})),
|
||||||
jobid:
|
jobid:
|
||||||
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
|
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
|
||||||
federal_tax_rate:
|
federal_tax_rate:
|
||||||
@@ -100,6 +105,7 @@ function BillEnterModalContainer({
|
|||||||
location,
|
location,
|
||||||
outstanding_returns,
|
outstanding_returns,
|
||||||
inventory,
|
inventory,
|
||||||
|
federal_tax_exempt,
|
||||||
...remainingValues
|
...remainingValues
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
@@ -120,6 +126,8 @@ function BillEnterModalContainer({
|
|||||||
lbr_adjustment,
|
lbr_adjustment,
|
||||||
location: lineLocation,
|
location: lineLocation,
|
||||||
part_type,
|
part_type,
|
||||||
|
create_ppc,
|
||||||
|
original_actual_price,
|
||||||
...restI
|
...restI
|
||||||
} = i;
|
} = i;
|
||||||
|
|
||||||
@@ -167,7 +175,7 @@ function BillEnterModalContainer({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
|
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@@ -292,6 +300,14 @@ function BillEnterModalContainer({
|
|||||||
location: li.location || location,
|
location: li.location || location,
|
||||||
status:
|
status:
|
||||||
bodyshop.md_order_statuses.default_received || "Received*",
|
bodyshop.md_order_statuses.default_received || "Received*",
|
||||||
|
//Added parts price changes.
|
||||||
|
...(li.create_ppc &&
|
||||||
|
li.original_actual_price !== li.actual_price
|
||||||
|
? {
|
||||||
|
act_price_before_ppc: li.original_actual_price,
|
||||||
|
act_price: li.actual_price,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -354,7 +370,9 @@ function BillEnterModalContainer({
|
|||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: values.jobid,
|
jobid: values.jobid,
|
||||||
billid: billId,
|
billid: billId,
|
||||||
operation: AuditTrailMapping.billposted(remainingValues.invoice_number),
|
operation: AuditTrailMapping.billposted(
|
||||||
|
r1.data.insert_bills.returning[0].invoice_number
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (enterAgain) {
|
if (enterAgain) {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export function BillFormComponent({
|
|||||||
job,
|
job,
|
||||||
loadOutstandingReturns,
|
loadOutstandingReturns,
|
||||||
loadInventory,
|
loadInventory,
|
||||||
|
preferredMake,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -78,6 +79,20 @@ export function BillFormComponent({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const handleFederalTaxExemptSwitchToggle = (checked) => {
|
||||||
|
// // Early gate
|
||||||
|
// if (!checked) return;
|
||||||
|
// const values = form.getFieldsValue("billlines");
|
||||||
|
// // Gate bill lines
|
||||||
|
// if (!values?.billlines?.length) return;
|
||||||
|
|
||||||
|
// const billlines = values.billlines.map((b) => {
|
||||||
|
// b.applicable_taxes.federal = false;
|
||||||
|
// return b;
|
||||||
|
// });
|
||||||
|
// form.setFieldsValue({ billlines });
|
||||||
|
// };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (job) form.validateFields(["is_credit_memo"]);
|
if (job) form.validateFields(["is_credit_memo"]);
|
||||||
}, [job, form]);
|
}, [job, form]);
|
||||||
@@ -185,6 +200,7 @@ export function BillFormComponent({
|
|||||||
<VendorSearchSelect
|
<VendorSearchSelect
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
options={vendorAutoCompleteOptions}
|
options={vendorAutoCompleteOptions}
|
||||||
|
preferredMake={preferredMake}
|
||||||
onSelect={handleVendorSelect}
|
onSelect={handleVendorSelect}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -389,7 +405,19 @@ export function BillFormComponent({
|
|||||||
// <CurrencyInput min={0} />
|
// <CurrencyInput min={0} />
|
||||||
// </Form.Item>
|
// </Form.Item>
|
||||||
}
|
}
|
||||||
<Form.Item shouldUpdate span={15}>
|
{
|
||||||
|
//Removed as a part of the merge to Rome Online. Federal tax not applicable.
|
||||||
|
// bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
|
||||||
|
// <Form.Item
|
||||||
|
// span={2}
|
||||||
|
// label={t("bills.labels.federal_tax_exempt")}
|
||||||
|
// name="federal_tax_exempt"
|
||||||
|
// >
|
||||||
|
// <Switch onChange={handleFederalTaxExemptSwitchToggle} />
|
||||||
|
// </Form.Item>
|
||||||
|
// ) : null
|
||||||
|
}
|
||||||
|
<Form.Item shouldUpdate span={13}>
|
||||||
{() => {
|
{() => {
|
||||||
const values = form.getFieldsValue([
|
const values = form.getFieldsValue([
|
||||||
"billlines",
|
"billlines",
|
||||||
@@ -407,7 +435,7 @@ export function BillFormComponent({
|
|||||||
totals = CalculateBillTotal(values);
|
totals = CalculateBillTotal(values);
|
||||||
if (!!totals)
|
if (!!totals)
|
||||||
return (
|
return (
|
||||||
<div>
|
<div align="right">
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("bills.labels.subtotal")}
|
title={t("bills.labels.subtotal")}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||||
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
||||||
import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries";
|
import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries";
|
||||||
|
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
|
||||||
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
|
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import BillFormComponent from "./bill-form.component";
|
|
||||||
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
|
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
|
||||||
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
|
|
||||||
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
|
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
|
||||||
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
import BillFormComponent from "./bill-form.component";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -59,6 +59,7 @@ export function BillFormContainer({
|
|||||||
disableInvNumber={disableInvNumber}
|
disableInvNumber={disableInvNumber}
|
||||||
loadOutstandingReturns={loadOutstandingReturns}
|
loadOutstandingReturns={loadOutstandingReturns}
|
||||||
loadInventory={loadInventory}
|
loadInventory={loadInventory}
|
||||||
|
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
||||||
/>
|
/>
|
||||||
{!billEdit && (
|
{!billEdit && (
|
||||||
<BillCmdReturnsTableComponent
|
<BillCmdReturnsTableComponent
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
|||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
Checkbox,
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
@@ -101,6 +102,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
line_desc: opt.line_desc,
|
line_desc: opt.line_desc,
|
||||||
quantity: opt.part_qty || 1,
|
quantity: opt.part_qty || 1,
|
||||||
actual_price: opt.cost,
|
actual_price: opt.cost,
|
||||||
|
original_actual_price: opt.cost,
|
||||||
cost_center: opt.part_type
|
cost_center: opt.part_type
|
||||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
||||||
? opt.part_type !== "PAE"
|
? opt.part_type !== "PAE"
|
||||||
@@ -227,6 +229,43 @@ export function BillEnterModalLinesComponent({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
additional: (record, index) => (
|
||||||
|
<Form.Item
|
||||||
|
dependencies={["billlines", record.name, "actual_price"]}
|
||||||
|
noStyle
|
||||||
|
>
|
||||||
|
{() => {
|
||||||
|
const billLine = getFieldValue(["billlines", record.name]);
|
||||||
|
const jobLine = lineData.find(
|
||||||
|
(line) => line.id === billLine?.joblineid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!billEdit &&
|
||||||
|
billLine &&
|
||||||
|
jobLine &&
|
||||||
|
billLine?.actual_price !== jobLine?.act_price
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<Space size="small">
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
label={t("joblines.fields.create_ppc")}
|
||||||
|
key={`${index}ppc`}
|
||||||
|
valuePropName="checked"
|
||||||
|
name={[record.name, "create_ppc"]}
|
||||||
|
>
|
||||||
|
<Checkbox />
|
||||||
|
</Form.Item>
|
||||||
|
{t("joblines.fields.create_ppc")}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.actual_cost"),
|
title: t("billlines.fields.actual_cost"),
|
||||||
@@ -368,7 +407,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
},
|
},
|
||||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||||
additional: (record, index) => (
|
additional: (record, index) => (
|
||||||
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
|
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
||||||
{() => {
|
{() => {
|
||||||
const price = getFieldValue([
|
const price = getFieldValue([
|
||||||
"billlines",
|
"billlines",
|
||||||
@@ -673,7 +712,7 @@ const EditableCell = ({
|
|||||||
if (additional)
|
if (additional)
|
||||||
return (
|
return (
|
||||||
<td {...restProps}>
|
<td {...restProps}>
|
||||||
<Space size="small">
|
<div size="small">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={dataIndex}
|
name={dataIndex}
|
||||||
labelCol={{ span: 0 }}
|
labelCol={{ span: 0 }}
|
||||||
@@ -682,7 +721,7 @@ const EditableCell = ({
|
|||||||
{(formInput && formInput(record, record.name)) || children}
|
{(formInput && formInput(record, record.name)) || children}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{additional && additional(record, record.name)}
|
{additional && additional(record, record.name)}
|
||||||
</Space>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
if (wrapper)
|
if (wrapper)
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { Button, Space } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
|
||||||
|
export default function BillPrintButton({ billid }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const Templates = TemplateList("job_special");
|
||||||
|
|
||||||
|
const submitHandler = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await GenerateDocument(
|
||||||
|
{
|
||||||
|
name: Templates.parts_invoice_label_single.key,
|
||||||
|
variables: {
|
||||||
|
id: billid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
"p"
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Warning: Error generating a document.");
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space wrap>
|
||||||
|
<Button loading={loading} onClick={submitHandler}>
|
||||||
|
{t("bills.labels.printlabels")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
|
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
|
||||||
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
|
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
|
||||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||||
@@ -58,7 +58,7 @@ export function BillsListTableComponent({
|
|||||||
<EditFilled />
|
<EditFilled />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<BillDeleteButton bill={record} />
|
<BillDeleteButton bill={record} jobid={job.id} />
|
||||||
<BillDetailEditReturnComponent
|
<BillDetailEditReturnComponent
|
||||||
data={{ bills_by_pk: { ...record, jobid: job.id } }}
|
data={{ bills_by_pk: { ...record, jobid: job.id } }}
|
||||||
disabled={
|
disabled={
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
|||||||
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
|
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
|
||||||
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
|
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
|
||||||
import ChatLabelComponent from "../chat-label/chat-label.component";
|
import ChatLabelComponent from "../chat-label/chat-label.component";
|
||||||
|
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
|
||||||
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
|
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
|
||||||
|
|
||||||
export default function ChatConversationTitle({ conversation }) {
|
export default function ChatConversationTitle({ conversation }) {
|
||||||
@@ -13,6 +14,7 @@ export default function ChatConversationTitle({ conversation }) {
|
|||||||
{conversation && conversation.phone_num}
|
{conversation && conversation.phone_num}
|
||||||
</PhoneNumberFormatter>
|
</PhoneNumberFormatter>
|
||||||
<ChatLabelComponent conversation={conversation} />
|
<ChatLabelComponent conversation={conversation} />
|
||||||
|
<ChatPrintButton conversation={conversation} />
|
||||||
<ChatConversationTitleTags
|
<ChatConversationTitleTags
|
||||||
jobConversations={
|
jobConversations={
|
||||||
(conversation && conversation.job_conversations) || []
|
(conversation && conversation.job_conversations) || []
|
||||||
|
|||||||
@@ -8,15 +8,23 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
|||||||
|
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
|
searchingForConversation: searchingForConversation,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ChatOpenButton({ bodyshop, phone, jobid, openChatByPhone }) {
|
export function ChatOpenButton({
|
||||||
|
bodyshop,
|
||||||
|
searchingForConversation,
|
||||||
|
phone,
|
||||||
|
jobid,
|
||||||
|
openChatByPhone,
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
if (!phone) return <></>;
|
if (!phone) return <></>;
|
||||||
|
|
||||||
@@ -29,7 +37,7 @@ export function ChatOpenButton({ bodyshop, phone, jobid, openChatByPhone }) {
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const p = parsePhoneNumber(phone, "CA");
|
const p = parsePhoneNumber(phone, "CA");
|
||||||
|
if (searchingForConversation) return; //This is to prevent finding the same thing twice.
|
||||||
if (p && p.isValid()) {
|
if (p && p.isValid()) {
|
||||||
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid });
|
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export function ChatPopupComponent({
|
|||||||
onClick={() => toggleChatVisible()}
|
onClick={() => toggleChatVisible()}
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<MessageOutlined />
|
<MessageOutlined className="chat-popup-info-icon" />
|
||||||
<strong>{t("messaging.labels.messaging")}</strong>
|
<strong>{t("messaging.labels.messaging")}</strong>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -13,6 +13,9 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.chat-popup-info-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (min-width: 992px) {
|
@media only screen and (min-width: 992px) {
|
||||||
.chat-popup {
|
.chat-popup {
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
|
||||||
|
import { Space, Spin } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||||
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function ChatPrintButton({ conversation }) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space wrap>
|
||||||
|
<PrinterOutlined
|
||||||
|
onClick={() => {
|
||||||
|
setLoading(true);
|
||||||
|
GenerateDocument(
|
||||||
|
{
|
||||||
|
name: TemplateList("messaging").conversation_list.key,
|
||||||
|
variables: { id: conversation.id },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: TemplateList("messaging").conversation_list.subject,
|
||||||
|
},
|
||||||
|
"p",
|
||||||
|
conversation.id
|
||||||
|
);
|
||||||
|
setLoading(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<MailOutlined
|
||||||
|
onClick={() => {
|
||||||
|
setLoading(true);
|
||||||
|
GenerateDocument(
|
||||||
|
{
|
||||||
|
name: TemplateList("messaging").conversation_list.key,
|
||||||
|
variables: { id: conversation.id },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subject: TemplateList("messaging").conversation_list.subject,
|
||||||
|
},
|
||||||
|
"e",
|
||||||
|
conversation.id
|
||||||
|
);
|
||||||
|
setLoading(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{loading && <Spin />}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatPrintButton);
|
||||||
@@ -35,6 +35,15 @@ export default function ContractsCarsComponent({
|
|||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
render: (text, record) => <div>{t(record.status)}</div>,
|
render: (text, record) => <div>{t(record.status)}</div>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("courtesycars.fields.readiness"),
|
||||||
|
dataIndex: "readiness",
|
||||||
|
key: "readiness",
|
||||||
|
sorter: (a, b) => alphaSort(a.readiness, b.readiness),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => t(record.readiness),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("courtesycars.fields.year"),
|
title: t("courtesycars.fields.year"),
|
||||||
dataIndex: "year",
|
dataIndex: "year",
|
||||||
|
|||||||
@@ -68,6 +68,30 @@ export default function ContractFormComponent({
|
|||||||
<FormDateTimePicker />
|
<FormDateTimePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
{create && (
|
||||||
|
<Form.Item
|
||||||
|
shouldUpdate={(p, c) => p.scheduledreturn !== c.scheduledreturn}
|
||||||
|
>
|
||||||
|
{() => {
|
||||||
|
const insuranceOver =
|
||||||
|
selectedCar &&
|
||||||
|
selectedCar.insuranceexpires &&
|
||||||
|
moment(selectedCar.insuranceexpires)
|
||||||
|
.endOf("day")
|
||||||
|
.isBefore(moment(form.getFieldValue("scheduledreturn")));
|
||||||
|
if (insuranceOver)
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||||
|
<span>
|
||||||
|
<WarningFilled style={{ marginRight: ".3rem" }} />
|
||||||
|
{t("contracts.labels.insuranceexpired")}
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
return <></>;
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -90,16 +114,17 @@ export default function ContractFormComponent({
|
|||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
const mileageOver =
|
const mileageOver =
|
||||||
selectedCar &&
|
selectedCar && selectedCar.nextservicekm
|
||||||
selectedCar.nextservicekm <= form.getFieldValue("kmstart");
|
? selectedCar.nextservicekm <= form.getFieldValue("kmstart")
|
||||||
|
: false;
|
||||||
const dueForService =
|
const dueForService =
|
||||||
selectedCar &&
|
selectedCar &&
|
||||||
selectedCar.nextservicedate &&
|
selectedCar.nextservicedate &&
|
||||||
moment(selectedCar.nextservicedate).isBefore(
|
moment(selectedCar.nextservicedate)
|
||||||
moment(form.getFieldValue("scheduledreturn"))
|
.endOf("day")
|
||||||
);
|
.isSameOrBefore(
|
||||||
|
moment(form.getFieldValue("scheduledreturn"))
|
||||||
|
);
|
||||||
if (mileageOver || dueForService)
|
if (mileageOver || dueForService)
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||||
@@ -117,7 +142,6 @@ export default function ContractFormComponent({
|
|||||||
</span>
|
</span>
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
}}
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
|
|
||||||
export default function ContractsJobsComponent({
|
export default function ContractsJobsComponent({
|
||||||
loading,
|
loading,
|
||||||
@@ -175,7 +176,7 @@ export default function ContractsJobsComponent({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{
|
pagination={{
|
||||||
position: "top",
|
position: "top",
|
||||||
defaultPageSize: 10,
|
defaultPageSize: pageLimit,
|
||||||
defaultCurrent: defaultCurrent,
|
defaultCurrent: defaultCurrent,
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { useQuery } from "@apollo/client";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import ContractJobsComponent from "./contract-jobs.component";
|
import ContractJobsComponent from "./contract-jobs.component";
|
||||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -15,6 +15,7 @@ export function ContractJobsContainer({ selectedJobState, bodyshop }) {
|
|||||||
const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||||
variables: {
|
variables: {
|
||||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"],
|
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"],
|
||||||
|
isConverted: true,
|
||||||
},
|
},
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ import queryString from "query-string";
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container";
|
import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
|
||||||
|
|
||||||
|
import moment from "moment";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import moment from "moment";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -209,7 +210,7 @@ export function ContractsList({
|
|||||||
}}
|
}}
|
||||||
pagination={{
|
pagination={{
|
||||||
position: "top",
|
position: "top",
|
||||||
pageSize: 25,
|
pageSize: pageLimit,
|
||||||
current: parseInt(page || 1),
|
current: parseInt(page || 1),
|
||||||
total: total,
|
total: total,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
|
|
||||||
export default function CourtesyCarContractListComponent({
|
export default function CourtesyCarContractListComponent({
|
||||||
contracts,
|
contracts,
|
||||||
@@ -89,7 +90,7 @@ export default function CourtesyCarContractListComponent({
|
|||||||
scroll={{ x: true }}
|
scroll={{ x: true }}
|
||||||
pagination={{
|
pagination={{
|
||||||
position: "top",
|
position: "top",
|
||||||
pageSize: 25,
|
pageSize: pageLimit,
|
||||||
current: parseInt(page || 1),
|
current: parseInt(page || 1),
|
||||||
total: totalContracts,
|
total: totalContracts,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { CHECK_CC_FLEET_NUMBER } from "../../graphql/courtesy-car.queries";
|
import { CHECK_CC_FLEET_NUMBER } from "../../graphql/courtesy-car.queries";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
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 CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||||
//import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
//import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
@@ -34,6 +35,18 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
|
|
||||||
{/* <FormFieldsChanged form={form} /> */}
|
{/* <FormFieldsChanged form={form} /> */}
|
||||||
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
|
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("courtesycars.fields.year")}
|
||||||
|
name="year"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("courtesycars.fields.make")}
|
label={t("courtesycars.fields.make")}
|
||||||
name="make"
|
name="make"
|
||||||
@@ -58,18 +71,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
>
|
>
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
label={t("courtesycars.fields.year")}
|
|
||||||
name="year"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("courtesycars.fields.plate")}
|
label={t("courtesycars.fields.plate")}
|
||||||
name="plate"
|
name="plate"
|
||||||
@@ -118,7 +119,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<InputNumber precision={0} />
|
<InputNumber min={0} precision={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("courtesycars.fields.fleetnumber")}
|
label={t("courtesycars.fields.fleetnumber")}
|
||||||
@@ -213,49 +214,27 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
>
|
>
|
||||||
<CourtesyCarStatus />
|
<CourtesyCarStatus />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item label={t("courtesycars.fields.readiness")} name="readiness">
|
||||||
label={t("courtesycars.fields.nextservicekm")}
|
<CourtesyCarReadiness />
|
||||||
name="nextservicekm"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("courtesycars.fields.nextservicedate")}
|
label={t("courtesycars.fields.nextservicekm")}
|
||||||
name="nextservicedate"
|
name="nextservicekm"
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<InputNumber min={0} precision={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
shouldUpdate={(p, c) =>
|
shouldUpdate={(p, c) =>
|
||||||
p.mileage !== c.mileage ||
|
p.mileage !== c.mileage || p.nextservicekm !== c.nextservicekm
|
||||||
p.nextservicedate !== c.nextservicedate ||
|
|
||||||
p.nextservicekm !== c.nextservicekm
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
const nextservicedate = form.getFieldValue("nextservicedate");
|
|
||||||
const nextservicekm = form.getFieldValue("nextservicekm");
|
const nextservicekm = form.getFieldValue("nextservicekm");
|
||||||
|
const mileageOver = nextservicekm
|
||||||
const mileageOver =
|
? nextservicekm <= form.getFieldValue("mileage")
|
||||||
nextservicekm <= form.getFieldValue("mileage");
|
: false;
|
||||||
|
if (mileageOver)
|
||||||
const dueForService =
|
|
||||||
nextservicedate && moment(nextservicedate).isBefore(moment());
|
|
||||||
|
|
||||||
if (mileageOver || dueForService)
|
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||||
<span>
|
<span>
|
||||||
@@ -263,6 +242,36 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
{t("contracts.labels.cardueforservice")}
|
{t("contracts.labels.cardueforservice")}
|
||||||
</span>
|
</span>
|
||||||
<span>{`${nextservicekm} km`}</span>
|
<span>{`${nextservicekm} km`}</span>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Form.Item
|
||||||
|
label={t("courtesycars.fields.nextservicedate")}
|
||||||
|
name="nextservicedate"
|
||||||
|
>
|
||||||
|
<FormDatePicker />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}
|
||||||
|
>
|
||||||
|
{() => {
|
||||||
|
const nextservicedate = form.getFieldValue("nextservicedate");
|
||||||
|
const dueForService =
|
||||||
|
nextservicedate &&
|
||||||
|
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
|
||||||
|
|
||||||
|
if (dueForService)
|
||||||
|
return (
|
||||||
|
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||||
|
<span>
|
||||||
|
<WarningFilled style={{ marginRight: ".3rem" }} />
|
||||||
|
{t("contracts.labels.cardueforservice")}
|
||||||
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<DateFormatter>{nextservicedate}</DateFormatter>
|
<DateFormatter>{nextservicedate}</DateFormatter>
|
||||||
</span>
|
</span>
|
||||||
@@ -283,12 +292,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("courtesycars.fields.registrationexpires")}
|
label={t("courtesycars.fields.registrationexpires")}
|
||||||
name="registrationexpires"
|
name="registrationexpires"
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<FormDatePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -300,7 +303,8 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
{() => {
|
{() => {
|
||||||
const expires = form.getFieldValue("registrationexpires");
|
const expires = form.getFieldValue("registrationexpires");
|
||||||
|
|
||||||
const dateover = expires && moment(expires).isBefore(moment());
|
const dateover =
|
||||||
|
expires && moment(expires).endOf("day").isBefore(moment());
|
||||||
|
|
||||||
if (dateover)
|
if (dateover)
|
||||||
return (
|
return (
|
||||||
@@ -330,14 +334,13 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
<FormDatePicker />
|
<FormDatePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
shouldUpdate={(p, c) =>
|
shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}
|
||||||
p.insuranceexpires !== c.insuranceexpires
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
const expires = form.getFieldValue("insuranceexpires");
|
const expires = form.getFieldValue("insuranceexpires");
|
||||||
|
|
||||||
const dateover = expires && moment(expires).isBefore(moment());
|
const dateover =
|
||||||
|
expires && moment(expires).endOf("day").isBefore(moment());
|
||||||
|
|
||||||
if (dateover)
|
if (dateover)
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -34,6 +34,32 @@ const CourtesyCarFuelComponent = (props, ref) => {
|
|||||||
step={null}
|
step={null}
|
||||||
style={{ marginLeft: "2rem", marginRight: "2rem" }}
|
style={{ marginLeft: "2rem", marginRight: "2rem" }}
|
||||||
{...props}
|
{...props}
|
||||||
|
tooltip={{
|
||||||
|
formatter: (value) => {
|
||||||
|
switch (value) {
|
||||||
|
case 0:
|
||||||
|
return t("courtesycars.labels.fuel.empty");
|
||||||
|
case 13:
|
||||||
|
return t("courtesycars.labels.fuel.18");
|
||||||
|
case 25:
|
||||||
|
return t("courtesycars.labels.fuel.14");
|
||||||
|
case 38:
|
||||||
|
return t("courtesycars.labels.fuel.38");
|
||||||
|
case 50:
|
||||||
|
return t("courtesycars.labels.fuel.12");
|
||||||
|
case 63:
|
||||||
|
return t("courtesycars.labels.fuel.58");
|
||||||
|
case 75:
|
||||||
|
return t("courtesycars.labels.fuel.34");
|
||||||
|
case 88:
|
||||||
|
return t("courtesycars.labels.fuel.78");
|
||||||
|
case 100:
|
||||||
|
return t("courtesycars.labels.fuel.full");
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Select } from "antd";
|
||||||
|
import React, { forwardRef, useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
const CourtesyCarReadinessComponent = ({ value, onChange }, ref) => {
|
||||||
|
const [option, setOption] = useState(value);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value !== option && onChange) {
|
||||||
|
onChange(option);
|
||||||
|
}
|
||||||
|
}, [value, option, onChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
ref={ref}
|
||||||
|
value={option}
|
||||||
|
style={{
|
||||||
|
width: 100,
|
||||||
|
}}
|
||||||
|
onChange={setOption}
|
||||||
|
>
|
||||||
|
<Option value="courtesycars.readiness.ready">
|
||||||
|
{t("courtesycars.readiness.ready")}
|
||||||
|
</Option>
|
||||||
|
<Option value="courtesycars.readiness.notready">
|
||||||
|
{t("courtesycars.readiness.notready")}
|
||||||
|
</Option>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default forwardRef(CourtesyCarReadinessComponent);
|
||||||
@@ -9,21 +9,26 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
|
import moment from "moment";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
|
||||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
|
||||||
import moment from "moment";
|
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
|
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
filteredInfo: { text: "" },
|
|
||||||
});
|
});
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
|
const [filter, setFilter] = useLocalStorage(
|
||||||
|
"filter_courtesy_cars_list",
|
||||||
|
null
|
||||||
|
);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@@ -50,6 +55,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
key: "status",
|
key: "status",
|
||||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||||
|
filteredValue: filter?.status || null,
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
text: t("courtesycars.status.in"),
|
text: t("courtesycars.status.in"),
|
||||||
@@ -72,18 +78,34 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
const { nextservicedate, nextservicekm, mileage } = record;
|
const { nextservicedate, nextservicekm, mileage, insuranceexpires } =
|
||||||
|
record;
|
||||||
|
|
||||||
const mileageOver = nextservicekm <= mileage;
|
const mileageOver = nextservicekm ? nextservicekm <= mileage : false;
|
||||||
|
|
||||||
const dueForService =
|
const dueForService =
|
||||||
nextservicedate && moment(nextservicedate).isBefore(moment());
|
nextservicedate &&
|
||||||
|
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
|
||||||
|
|
||||||
|
const insuranceOver =
|
||||||
|
insuranceexpires &&
|
||||||
|
moment(insuranceexpires).endOf("day").isBefore(moment());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
{t(record.status)}
|
{t(record.status)}
|
||||||
{(mileageOver || dueForService) && (
|
{(mileageOver || dueForService || insuranceOver) && (
|
||||||
<Tooltip title={t("contracts.labels.cardueforservice")}>
|
<Tooltip
|
||||||
|
title={
|
||||||
|
(mileageOver || dueForService) && insuranceOver
|
||||||
|
? t("contracts.labels.insuranceexpired") +
|
||||||
|
" / " +
|
||||||
|
t("contracts.labels.cardueforservice")
|
||||||
|
: insuranceOver
|
||||||
|
? t("contracts.labels.insuranceexpired")
|
||||||
|
: t("contracts.labels.cardueforservice")
|
||||||
|
}
|
||||||
|
>
|
||||||
<WarningFilled style={{ color: "tomato" }} />
|
<WarningFilled style={{ color: "tomato" }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
@@ -91,6 +113,27 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("courtesycars.fields.readiness"),
|
||||||
|
dataIndex: "readiness",
|
||||||
|
key: "readiness",
|
||||||
|
sorter: (a, b) => alphaSort(a.readiness, b.readiness),
|
||||||
|
filteredValue: filter?.readiness || null,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: t("courtesycars.readiness.ready"),
|
||||||
|
value: "courtesycars.readiness.ready",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t("courtesycars.readiness.notready"),
|
||||||
|
value: "courtesycars.readiness.notready",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onFilter: (value, record) => value.includes(record.readiness),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => t(record.readiness),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("courtesycars.fields.year"),
|
title: t("courtesycars.fields.year"),
|
||||||
dataIndex: "year",
|
dataIndex: "year",
|
||||||
@@ -115,6 +158,14 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "model" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "model" && state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("courtesycars.fields.color"),
|
||||||
|
dataIndex: "color",
|
||||||
|
key: "color",
|
||||||
|
sorter: (a, b) => alphaSort(a.color, b.color),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "color" && state.sortedInfo.order,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("courtesycars.fields.plate"),
|
title: t("courtesycars.fields.plate"),
|
||||||
dataIndex: "plate",
|
dataIndex: "plate",
|
||||||
@@ -123,6 +174,36 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "plate" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "plate" && state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("courtesycars.fields.fuel"),
|
||||||
|
dataIndex: "fuel",
|
||||||
|
key: "fuel",
|
||||||
|
sorter: (a, b) => alphaSort(a.fuel, b.fuel),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => {
|
||||||
|
switch (record.fuel) {
|
||||||
|
case 100:
|
||||||
|
return t("courtesycars.labels.fuel.full");
|
||||||
|
case 88:
|
||||||
|
return t("courtesycars.labels.fuel.78");
|
||||||
|
case 63:
|
||||||
|
return t("courtesycars.labels.fuel.58");
|
||||||
|
case 50:
|
||||||
|
return t("courtesycars.labels.fuel.12");
|
||||||
|
case 38:
|
||||||
|
return t("courtesycars.labels.fuel.34");
|
||||||
|
case 25:
|
||||||
|
return t("courtesycars.labels.fuel.14");
|
||||||
|
case 13:
|
||||||
|
return t("courtesycars.labels.fuel.18");
|
||||||
|
case 0:
|
||||||
|
return t("courtesycars.labels.fuel.empty");
|
||||||
|
default:
|
||||||
|
return record.fuel;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("courtesycars.labels.outwith"),
|
title: t("courtesycars.labels.outwith"),
|
||||||
dataIndex: "outwith",
|
dataIndex: "outwith",
|
||||||
@@ -153,7 +234,8 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, sortedInfo: sorter });
|
||||||
|
setFilter(filters);
|
||||||
};
|
};
|
||||||
|
|
||||||
const tableData = searchText
|
const tableData = searchText
|
||||||
@@ -166,6 +248,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
(c.year || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(c.year || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(c.make || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(c.make || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(c.model || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
(c.model || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
|
(c.plate || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||||
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
|
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
|
||||||
)
|
)
|
||||||
: courtesycars;
|
: courtesycars;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import React, { useEffect } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { QUERY_CSI_RESPONSE_BY_PK } from "../../graphql/csi.queries";
|
import { QUERY_CSI_RESPONSE_BY_PK } from "../../graphql/csi.queries";
|
||||||
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import ConfigFormComponents from "../config-form-components/config-form-components.component";
|
import ConfigFormComponents from "../config-form-components/config-form-components.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
@@ -44,6 +45,13 @@ export default function CsiResponseFormContainer() {
|
|||||||
readOnly
|
readOnly
|
||||||
componentList={data.csi_by_pk.csiquestion.config}
|
componentList={data.csi_by_pk.csiquestion.config}
|
||||||
/>
|
/>
|
||||||
|
{data.csi_by_pk.validuntil ? (
|
||||||
|
<>
|
||||||
|
{t("csi.fields.validuntil")}
|
||||||
|
{": "}
|
||||||
|
<DateFormatter>{data.csi_by_pk.validuntil}</DateFormatter>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { pageLimit } from "../../utils/config";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
|
import OwnerNameDisplay, {
|
||||||
|
OwnerNameDisplayFunction,
|
||||||
|
} from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
export default function CsiResponseListPaginated({
|
export default function CsiResponseListPaginated({
|
||||||
refetch,
|
refetch,
|
||||||
@@ -15,23 +18,23 @@ export default function CsiResponseListPaginated({
|
|||||||
total,
|
total,
|
||||||
}) {
|
}) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const { responseid, page, sortcolumn, sortorder } = search;
|
const { responseid } = search;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
filteredInfo: { text: "" },
|
filteredInfo: { text: "" },
|
||||||
|
page: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
dataIndex: "ro_number",
|
dataIndex: "ro_number",
|
||||||
key: "ro_number",
|
key: "ro_number",
|
||||||
width: "8%",
|
|
||||||
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
|
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
|
||||||
sortOrder: sortcolumn === "ro_number" && sortorder,
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link to={"/manage/jobs/" + record.job.id}>
|
<Link to={"/manage/jobs/" + record.job.id}>
|
||||||
{record.job.ro_number || t("general.labels.na")}
|
{record.job.ro_number || t("general.labels.na")}
|
||||||
@@ -40,15 +43,18 @@ export default function CsiResponseListPaginated({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.owner"),
|
title: t("jobs.fields.owner"),
|
||||||
dataIndex: "owner",
|
dataIndex: "owner_name",
|
||||||
key: "owner",
|
key: "owner_name",
|
||||||
ellipsis: true,
|
sorter: (a, b) =>
|
||||||
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
|
alphaSort(
|
||||||
width: "25%",
|
OwnerNameDisplayFunction(a.job),
|
||||||
sortOrder: sortcolumn === "owner" && sortorder,
|
OwnerNameDisplayFunction(b.job)
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "owner_name" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.job.owner ? (
|
return record.job.ownerid ? (
|
||||||
<Link to={"/manage/owners/" + record.job.owner.id}>
|
<Link to={"/manage/owners/" + record.job.ownerid}>
|
||||||
<OwnerNameDisplay ownerObject={record.job} />
|
<OwnerNameDisplay ownerObject={record.job} />
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
@@ -63,9 +69,9 @@ export default function CsiResponseListPaginated({
|
|||||||
dataIndex: "completedon",
|
dataIndex: "completedon",
|
||||||
key: "completedon",
|
key: "completedon",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: (a, b) => a.completedon - b.completedon,
|
sorter: (a, b) => dateSort(a.completedon, b.completedon),
|
||||||
width: "25%",
|
sortOrder:
|
||||||
sortOrder: sortcolumn === "completedon" && sortorder,
|
state.sortedInfo.columnKey === "completedon" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.completedon ? (
|
return record.completedon ? (
|
||||||
<DateFormatter>{record.completedon}</DateFormatter>
|
<DateFormatter>{record.completedon}</DateFormatter>
|
||||||
@@ -75,11 +81,12 @@ export default function CsiResponseListPaginated({
|
|||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({
|
||||||
search.page = pagination.current;
|
...state,
|
||||||
search.sortcolumn = sorter.columnKey;
|
filteredInfo: filters,
|
||||||
search.sortorder = sorter.order;
|
sortedInfo: sorter,
|
||||||
history.push({ search: queryString.stringify(search) });
|
page: pagination.current,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnRowClick = (record) => {
|
const handleOnRowClick = (record) => {
|
||||||
@@ -106,8 +113,8 @@ export default function CsiResponseListPaginated({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{
|
pagination={{
|
||||||
position: "top",
|
position: "top",
|
||||||
pageSize: 25,
|
pageSize: pageLimit,
|
||||||
current: parseInt(page || 1),
|
current: parseInt(state.page || 1),
|
||||||
total: total,
|
total: total,
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -121,13 +128,6 @@ export default function CsiResponseListPaginated({
|
|||||||
selectedRowKeys: [responseid],
|
selectedRowKeys: [responseid],
|
||||||
type: "radio",
|
type: "radio",
|
||||||
}}
|
}}
|
||||||
onRow={(record, rowIndex) => {
|
|
||||||
return {
|
|
||||||
onClick: (event) => {
|
|
||||||
handleOnRowClick(record);
|
|
||||||
}, // click row
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { alphaSort } from "../../../utils/sorters";
|
|||||||
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import DashboardRefreshRequired from "../refresh-required.component";
|
import DashboardRefreshRequired from "../refresh-required.component";
|
||||||
|
import {pageLimit} from "../../../utils/config";
|
||||||
|
|
||||||
export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -118,7 +119,7 @@ export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
|||||||
<div style={{ height: "100%" }}>
|
<div style={{ height: "100%" }}>
|
||||||
<Table
|
<Table
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
scroll={{ x: true, y: "calc(100% - 4em)" }}
|
scroll={{ x: true, y: "calc(100% - 4em)" }}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
|
|||||||
@@ -3,20 +3,32 @@ import {
|
|||||||
ExclamationCircleFilled,
|
ExclamationCircleFilled,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Card, Space, Table, Tooltip } from "antd";
|
import { Card, Space, Switch, Table, Tooltip, Typography } from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { TimeFormatter } from "../../../utils/DateFormatter";
|
||||||
|
import { onlyUnique } from "../../../utils/arrayHelper";
|
||||||
|
import { alphaSort, dateSort } from "../../../utils/sorters";
|
||||||
|
import useLocalStorage from "../../../utils/useLocalStorage";
|
||||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, {
|
||||||
|
OwnerNameDisplayFunction,
|
||||||
|
} from "../../owner-name-display/owner-name-display.component";
|
||||||
import DashboardRefreshRequired from "../refresh-required.component";
|
import DashboardRefreshRequired from "../refresh-required.component";
|
||||||
|
|
||||||
export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
|
filteredInfo: {},
|
||||||
});
|
});
|
||||||
|
const [isTvModeScheduledIn, setIsTvModeScheduledIn] = useLocalStorage(
|
||||||
|
"isTvModeScheduledIn",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
if (!data.scheduled_in_today)
|
if (!data.scheduled_in_today)
|
||||||
return <DashboardRefreshRequired {...cardProps} />;
|
return <DashboardRefreshRequired {...cardProps} />;
|
||||||
@@ -30,6 +42,12 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
alt_transport: item.job.alt_transport,
|
alt_transport: item.job.alt_transport,
|
||||||
clm_no: item.job.clm_no,
|
clm_no: item.job.clm_no,
|
||||||
jobid: item.job.jobid,
|
jobid: item.job.jobid,
|
||||||
|
joblines_body: item.job.joblines
|
||||||
|
.filter((l) => l.mod_lbr_ty !== "LAR")
|
||||||
|
.reduce((acc, val) => acc + val.mod_lb_hrs, 0),
|
||||||
|
joblines_ref: item.job.joblines
|
||||||
|
.filter((l) => l.mod_lbr_ty === "LAR")
|
||||||
|
.reduce((acc, val) => acc + val.mod_lb_hrs, 0),
|
||||||
ins_co_nm: item.job.ins_co_nm,
|
ins_co_nm: item.job.ins_co_nm,
|
||||||
iouparent: item.job.iouparent,
|
iouparent: item.job.iouparent,
|
||||||
ownerid: item.job.ownerid,
|
ownerid: item.job.ownerid,
|
||||||
@@ -48,7 +66,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
v_vin: item.job.v_vin,
|
v_vin: item.job.v_vin,
|
||||||
vehicleid: item.job.vehicleid,
|
vehicleid: item.job.vehicleid,
|
||||||
note: item.note,
|
note: item.note,
|
||||||
start: moment(item.start).format("hh:mm a"),
|
start: item.start,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
};
|
};
|
||||||
appt.push(i);
|
appt.push(i);
|
||||||
@@ -58,11 +76,192 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
return new moment(a.start) - new moment(b.start);
|
return new moment(a.start) - new moment(b.start);
|
||||||
});
|
});
|
||||||
|
|
||||||
const columns = [
|
const tvFontSize = 16;
|
||||||
|
const tvFontWeight = "bold";
|
||||||
|
|
||||||
|
const tvColumns = [
|
||||||
|
{
|
||||||
|
title: t("appointments.fields.time"),
|
||||||
|
dataIndex: "start",
|
||||||
|
key: "start",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) => dateSort(a.start, b.start),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "start" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
<TimeFormatter>{record.start}</TimeFormatter>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
dataIndex: "ro_number",
|
dataIndex: "ro_number",
|
||||||
key: "ro_number",
|
key: "ro_number",
|
||||||
|
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<Link
|
||||||
|
to={"/manage/jobs/" + record.jobid}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{record.ro_number || t("general.labels.na")}
|
||||||
|
{record.production_vars && record.production_vars.alert ? (
|
||||||
|
<ExclamationCircleFilled className="production-alert" />
|
||||||
|
) : null}
|
||||||
|
{record.suspended && (
|
||||||
|
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||||
|
)}
|
||||||
|
{record.iouparent && (
|
||||||
|
<Tooltip title={t("jobs.labels.iou")}>
|
||||||
|
<BranchesOutlined style={{ color: "orangered" }} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.owner"),
|
||||||
|
dataIndex: "owner",
|
||||||
|
key: "owner",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.ownerid ? (
|
||||||
|
<Link
|
||||||
|
to={"/manage/owners/" + record.ownerid}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
<OwnerNameDisplay ownerObject={record} />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
<OwnerNameDisplay ownerObject={record} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.vehicle"),
|
||||||
|
dataIndex: "vehicle",
|
||||||
|
key: "vehicle",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||||
|
a.v_model_desc || ""
|
||||||
|
}`,
|
||||||
|
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.vehicleid ? (
|
||||||
|
<Link
|
||||||
|
to={"/manage/vehicles/" + record.vehicleid}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||||
|
record.v_model_desc || ""
|
||||||
|
}`}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{`${
|
||||||
|
record.v_model_yr || ""
|
||||||
|
} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("appointments.fields.alt_transport"),
|
||||||
|
dataIndex: "alt_transport",
|
||||||
|
key: "alt_transport",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "alt_transport" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
filters:
|
||||||
|
(appt &&
|
||||||
|
appt
|
||||||
|
.map((j) => j.alt_transport)
|
||||||
|
.filter(onlyUnique)
|
||||||
|
.map((s) => {
|
||||||
|
return {
|
||||||
|
text: s || "No Alt. Transport",
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
|
[],
|
||||||
|
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||||
|
render: (text, record) => (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{record.alt_transport}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.lab"),
|
||||||
|
dataIndex: "joblines_body",
|
||||||
|
key: "joblines_body",
|
||||||
|
sorter: (a, b) => a.joblines_body - b.joblines_body,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "joblines_body" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
align: "right",
|
||||||
|
render: (text, record) => (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{record.joblines_body.toFixed(1)}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.lar"),
|
||||||
|
dataIndex: "joblines_ref",
|
||||||
|
key: "joblines_ref",
|
||||||
|
sorter: (a, b) => a.joblines_ref - b.joblines_ref,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order,
|
||||||
|
align: "right",
|
||||||
|
render: (text, record) => (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{record.joblines_ref.toFixed(1)}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("appointments.fields.time"),
|
||||||
|
dataIndex: "start",
|
||||||
|
key: "start",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) => dateSort(a.start, b.start),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "start" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => <TimeFormatter>{record.start}</TimeFormatter>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.ro_number"),
|
||||||
|
dataIndex: "ro_number",
|
||||||
|
key: "ro_number",
|
||||||
|
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link
|
<Link
|
||||||
to={"/manage/jobs/" + record.jobid}
|
to={"/manage/jobs/" + record.jobid}
|
||||||
@@ -90,7 +289,10 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
dataIndex: "owner",
|
dataIndex: "owner",
|
||||||
key: "owner",
|
key: "owner",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
sorter: (a, b) =>
|
||||||
|
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.ownerid ? (
|
return record.ownerid ? (
|
||||||
<Link
|
<Link
|
||||||
@@ -107,23 +309,16 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ownr_ph1"),
|
title: t("dashboard.labels.phone"),
|
||||||
dataIndex: "ownr_ph1",
|
dataIndex: "ownr_ph",
|
||||||
key: "ownr_ph1",
|
key: "ownr_ph",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
<Space size="small" wrap>
|
||||||
),
|
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||||
},
|
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||||
{
|
</Space>
|
||||||
title: t("jobs.fields.ownr_ph2"),
|
|
||||||
dataIndex: "ownr_ph2",
|
|
||||||
key: "ownr_ph2",
|
|
||||||
ellipsis: true,
|
|
||||||
responsive: ["md"],
|
|
||||||
render: (text, record) => (
|
|
||||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -133,7 +328,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
|
<a href={`mailto:${record.ownr_ea}`}>{record.ownr_ea}</a>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -141,6 +336,15 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
dataIndex: "vehicle",
|
dataIndex: "vehicle",
|
||||||
key: "vehicle",
|
key: "vehicle",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||||
|
a.v_model_desc || ""
|
||||||
|
}`,
|
||||||
|
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.vehicleid ? (
|
return record.vehicleid ? (
|
||||||
<Link
|
<Link
|
||||||
@@ -164,43 +368,80 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
key: "ins_co_nm",
|
key: "ins_co_nm",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
},
|
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
|
||||||
{
|
sortOrder:
|
||||||
title: t("appointments.fields.time"),
|
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
|
||||||
dataIndex: "start",
|
filters:
|
||||||
key: "start",
|
(appt &&
|
||||||
ellipsis: true,
|
appt
|
||||||
responsive: ["md"],
|
.map((j) => j.ins_co_nm)
|
||||||
|
.filter(onlyUnique)
|
||||||
|
.map((s) => {
|
||||||
|
return {
|
||||||
|
text: s || "No Ins. Co.*",
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
|
[],
|
||||||
|
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("appointments.fields.alt_transport"),
|
title: t("appointments.fields.alt_transport"),
|
||||||
dataIndex: "alt_transport",
|
dataIndex: "alt_transport",
|
||||||
key: "alt_transport",
|
key: "alt_transport",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "alt_transport" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
filters:
|
||||||
|
(appt &&
|
||||||
|
appt
|
||||||
|
.map((j) => j.alt_transport)
|
||||||
|
.filter(onlyUnique)
|
||||||
|
.map((s) => {
|
||||||
|
return {
|
||||||
|
text: s || "No Alt. Transport",
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
|
[],
|
||||||
|
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={t("dashboard.titles.scheduledintoday", {
|
title={t("dashboard.titles.scheduledindate", {
|
||||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||||
})}
|
})}
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Typography.Text>{t("general.labels.tvmode")}</Typography.Text>
|
||||||
|
<Switch
|
||||||
|
onClick={() => setIsTvModeScheduledIn(!isTvModeScheduledIn)}
|
||||||
|
defaultChecked={isTvModeScheduledIn}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
{...cardProps}
|
{...cardProps}
|
||||||
>
|
>
|
||||||
<div style={{ height: "100%" }}>
|
<div style={{ height: "100%" }}>
|
||||||
<Table
|
<Table
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
pagination={false}
|
||||||
columns={columns}
|
columns={isTvModeScheduledIn ? tvColumns : columns}
|
||||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
style={{ height: "85%" }}
|
style={{ height: "85%" }}
|
||||||
dataSource={appt}
|
dataSource={appt}
|
||||||
|
size={isTvModeScheduledIn ? "small" : "middle"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -219,6 +460,10 @@ export const DashboardScheduledInTodayGql = `
|
|||||||
alt_transport
|
alt_transport
|
||||||
clm_no
|
clm_no
|
||||||
jobid: id
|
jobid: id
|
||||||
|
joblines(where: {removed: {_eq: false}}) {
|
||||||
|
mod_lb_hrs
|
||||||
|
mod_lbr_ty
|
||||||
|
}
|
||||||
ins_co_nm
|
ins_co_nm
|
||||||
iouparent
|
iouparent
|
||||||
ownerid
|
ownerid
|
||||||
|
|||||||
@@ -3,36 +3,272 @@ import {
|
|||||||
ExclamationCircleFilled,
|
ExclamationCircleFilled,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Card, Space, Table, Tooltip } from "antd";
|
import { Card, Space, Switch, Table, Tooltip, Typography } from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { TimeFormatter } from "../../../utils/DateFormatter";
|
||||||
|
import { onlyUnique } from "../../../utils/arrayHelper";
|
||||||
|
import { alphaSort, dateSort } from "../../../utils/sorters";
|
||||||
|
import useLocalStorage from "../../../utils/useLocalStorage";
|
||||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, {
|
||||||
|
OwnerNameDisplayFunction,
|
||||||
|
} from "../../owner-name-display/owner-name-display.component";
|
||||||
import DashboardRefreshRequired from "../refresh-required.component";
|
import DashboardRefreshRequired from "../refresh-required.component";
|
||||||
|
|
||||||
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
|
filteredInfo: {},
|
||||||
});
|
});
|
||||||
|
const [isTvModeScheduledOut, setIsTvModeScheduledOut] = useLocalStorage(
|
||||||
|
"isTvModeScheduledOut",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
if (!data.scheduled_out_today)
|
if (!data.scheduled_out_today)
|
||||||
return <DashboardRefreshRequired {...cardProps} />;
|
return <DashboardRefreshRequired {...cardProps} />;
|
||||||
|
|
||||||
data.scheduled_out_today.forEach((item) => {
|
data.scheduled_out_today.forEach((item) => {
|
||||||
item.scheduled_completion= moment(item.scheduled_completion).format("hh:mm a")
|
item.joblines_body = item.joblines
|
||||||
|
? item.joblines
|
||||||
|
.filter((l) => l.mod_lbr_ty !== "LAR")
|
||||||
|
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
|
||||||
|
: 0;
|
||||||
|
item.joblines_ref = item.joblines
|
||||||
|
? item.joblines
|
||||||
|
.filter((l) => l.mod_lbr_ty === "LAR")
|
||||||
|
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
|
||||||
|
: 0;
|
||||||
});
|
});
|
||||||
data.scheduled_out_today.sort(function (a, b) {
|
data.scheduled_out_today.sort(function (a, b) {
|
||||||
return new Date(a.scheduled_completion) - new Date(b.scheduled_completion);
|
return new Date(a.scheduled_completion) - new Date(b.scheduled_completion);
|
||||||
});
|
});
|
||||||
|
|
||||||
const columns = [
|
const tvFontSize = 18;
|
||||||
|
const tvFontWeight = "bold";
|
||||||
|
|
||||||
|
const tvColumns = [
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.scheduled_completion"),
|
||||||
|
dataIndex: "scheduled_completion",
|
||||||
|
key: "scheduled_completion",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
dateSort(a.scheduled_completion, b.scheduled_completion),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "scheduled_completion" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
dataIndex: "ro_number",
|
dataIndex: "ro_number",
|
||||||
key: "ro_number",
|
key: "ro_number",
|
||||||
|
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<Link
|
||||||
|
to={"/manage/jobs/" + record.jobid}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{record.ro_number || t("general.labels.na")}
|
||||||
|
{record.production_vars && record.production_vars.alert ? (
|
||||||
|
<ExclamationCircleFilled className="production-alert" />
|
||||||
|
) : null}
|
||||||
|
{record.suspended && (
|
||||||
|
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||||
|
)}
|
||||||
|
{record.iouparent && (
|
||||||
|
<Tooltip title={t("jobs.labels.iou")}>
|
||||||
|
<BranchesOutlined style={{ color: "orangered" }} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.owner"),
|
||||||
|
dataIndex: "owner",
|
||||||
|
key: "owner",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.ownerid ? (
|
||||||
|
<Link
|
||||||
|
to={"/manage/owners/" + record.ownerid}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
<OwnerNameDisplay ownerObject={record} />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
<OwnerNameDisplay ownerObject={record} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.vehicle"),
|
||||||
|
dataIndex: "vehicle",
|
||||||
|
key: "vehicle",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||||
|
a.v_model_desc || ""
|
||||||
|
}`,
|
||||||
|
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.vehicleid ? (
|
||||||
|
<Link
|
||||||
|
to={"/manage/vehicles/" + record.vehicleid}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||||
|
record.v_model_desc || ""
|
||||||
|
}`}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{`${
|
||||||
|
record.v_model_yr || ""
|
||||||
|
} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("appointments.fields.alt_transport"),
|
||||||
|
dataIndex: "alt_transport",
|
||||||
|
key: "alt_transport",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "alt_transport" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
filters:
|
||||||
|
(data.scheduled_out_today &&
|
||||||
|
data.scheduled_out_today
|
||||||
|
.map((j) => j.alt_transport)
|
||||||
|
.filter(onlyUnique)
|
||||||
|
.map((s) => {
|
||||||
|
return {
|
||||||
|
text: s || "No Alt. Transport*",
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
|
[],
|
||||||
|
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||||
|
render: (text, record) => (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{record.alt_transport}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.status"),
|
||||||
|
dataIndex: "status",
|
||||||
|
key: "status",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
|
filters:
|
||||||
|
(data.scheduled_out_today &&
|
||||||
|
data.scheduled_out_today
|
||||||
|
.map((j) => j.status)
|
||||||
|
.filter(onlyUnique)
|
||||||
|
.map((s) => {
|
||||||
|
return {
|
||||||
|
text: s || "No Status*",
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
|
[],
|
||||||
|
onFilter: (value, record) => value.includes(record.status),
|
||||||
|
render: (text, record) => (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{record.status}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.lab"),
|
||||||
|
dataIndex: "joblines_body",
|
||||||
|
key: "joblines_body",
|
||||||
|
sorter: (a, b) => a.joblines_body - b.joblines_body,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "joblines_body" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
align: "right",
|
||||||
|
render: (text, record) => (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{record.joblines_body.toFixed(1)}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.lar"),
|
||||||
|
dataIndex: "joblines_ref",
|
||||||
|
key: "joblines_ref",
|
||||||
|
sorter: (a, b) => a.joblines_ref - b.joblines_ref,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order,
|
||||||
|
align: "right",
|
||||||
|
render: (text, record) => (
|
||||||
|
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||||
|
{record.joblines_ref.toFixed(1)}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.scheduled_completion"),
|
||||||
|
dataIndex: "scheduled_completion",
|
||||||
|
key: "scheduled_completion",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
dateSort(a.scheduled_completion, b.scheduled_completion),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "scheduled_completion" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.ro_number"),
|
||||||
|
dataIndex: "ro_number",
|
||||||
|
key: "ro_number",
|
||||||
|
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link
|
<Link
|
||||||
to={"/manage/jobs/" + record.jobid}
|
to={"/manage/jobs/" + record.jobid}
|
||||||
@@ -60,7 +296,10 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
|||||||
dataIndex: "owner",
|
dataIndex: "owner",
|
||||||
key: "owner",
|
key: "owner",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
sorter: (a, b) =>
|
||||||
|
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.ownerid ? (
|
return record.ownerid ? (
|
||||||
<Link
|
<Link
|
||||||
@@ -77,23 +316,16 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ownr_ph1"),
|
title: t("dashboard.labels.phone"),
|
||||||
dataIndex: "ownr_ph1",
|
dataIndex: "ownr_ph",
|
||||||
key: "ownr_ph1",
|
key: "ownr_ph",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
<Space size="small" wrap>
|
||||||
),
|
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||||
},
|
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||||
{
|
</Space>
|
||||||
title: t("jobs.fields.ownr_ph2"),
|
|
||||||
dataIndex: "ownr_ph2",
|
|
||||||
key: "ownr_ph2",
|
|
||||||
ellipsis: true,
|
|
||||||
responsive: ["md"],
|
|
||||||
render: (text, record) => (
|
|
||||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -103,7 +335,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
|
<a href={`mailto:${record.ownr_ea}`}>{record.ownr_ea}</a>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -111,6 +343,15 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
|||||||
dataIndex: "vehicle",
|
dataIndex: "vehicle",
|
||||||
key: "vehicle",
|
key: "vehicle",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
sorter: (a, b) =>
|
||||||
|
alphaSort(
|
||||||
|
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||||
|
a.v_model_desc || ""
|
||||||
|
}`,
|
||||||
|
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||||
|
),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.vehicleid ? (
|
return record.vehicleid ? (
|
||||||
<Link
|
<Link
|
||||||
@@ -134,43 +375,80 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
|||||||
key: "ins_co_nm",
|
key: "ins_co_nm",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
},
|
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
|
||||||
{
|
sortOrder:
|
||||||
title: t("jobs.fields.scheduled_completion"),
|
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
|
||||||
dataIndex: "scheduled_completion",
|
filters:
|
||||||
key: "scheduled_completion",
|
(data.scheduled_out_today &&
|
||||||
ellipsis: true,
|
data.scheduled_out_today
|
||||||
responsive: ["md"],
|
.map((j) => j.ins_co_nm)
|
||||||
|
.filter(onlyUnique)
|
||||||
|
.map((s) => {
|
||||||
|
return {
|
||||||
|
text: s || "No Ins. Co.*",
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
|
[],
|
||||||
|
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("appointments.fields.alt_transport"),
|
title: t("appointments.fields.alt_transport"),
|
||||||
dataIndex: "alt_transport",
|
dataIndex: "alt_transport",
|
||||||
key: "alt_transport",
|
key: "alt_transport",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["md"],
|
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "alt_transport" &&
|
||||||
|
state.sortedInfo.order,
|
||||||
|
filters:
|
||||||
|
(data.scheduled_out_today &&
|
||||||
|
data.scheduled_out_today
|
||||||
|
.map((j) => j.alt_transport)
|
||||||
|
.filter(onlyUnique)
|
||||||
|
.map((s) => {
|
||||||
|
return {
|
||||||
|
text: s || "No Alt. Transport*",
|
||||||
|
value: [s],
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
|
[],
|
||||||
|
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={t("dashboard.titles.scheduledouttoday", {
|
title={t("dashboard.titles.scheduledoutdate", {
|
||||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||||
})}
|
})}
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Typography.Text>{t("general.labels.tvmode")}</Typography.Text>
|
||||||
|
<Switch
|
||||||
|
onClick={() => setIsTvModeScheduledOut(!isTvModeScheduledOut)}
|
||||||
|
defaultChecked={isTvModeScheduledOut}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
{...cardProps}
|
{...cardProps}
|
||||||
>
|
>
|
||||||
<div style={{ height: "100%" }}>
|
<div style={{ height: "100%" }}>
|
||||||
<Table
|
<Table
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
pagination={false}
|
||||||
columns={columns}
|
columns={isTvModeScheduledOut ? tvColumns : columns}
|
||||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
style={{ height: "85%" }}
|
style={{ height: "85%" }}
|
||||||
dataSource={data.scheduled_out_today}
|
dataSource={data.scheduled_out_today}
|
||||||
|
size={isTvModeScheduledOut ? "small" : "middle"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -187,6 +465,10 @@ export const DashboardScheduledOutTodayGql = `
|
|||||||
alt_transport
|
alt_transport
|
||||||
clm_no
|
clm_no
|
||||||
jobid: id
|
jobid: id
|
||||||
|
joblines(where: {removed: {_eq: false}}) {
|
||||||
|
mod_lb_hrs
|
||||||
|
mod_lbr_ty
|
||||||
|
}
|
||||||
ins_co_nm
|
ins_co_nm
|
||||||
iouparent
|
iouparent
|
||||||
ownerid
|
ownerid
|
||||||
@@ -199,6 +481,7 @@ export const DashboardScheduledOutTodayGql = `
|
|||||||
production_vars
|
production_vars
|
||||||
ro_number
|
ro_number
|
||||||
scheduled_completion
|
scheduled_completion
|
||||||
|
status
|
||||||
suspended
|
suspended
|
||||||
v_make_desc
|
v_make_desc
|
||||||
v_model_desc
|
v_model_desc
|
||||||
|
|||||||
@@ -275,26 +275,22 @@ const componentList = {
|
|||||||
h: 2,
|
h: 2,
|
||||||
},
|
},
|
||||||
ScheduleInToday: {
|
ScheduleInToday: {
|
||||||
label: i18next.t("dashboard.titles.scheduledintoday", {
|
label: i18next.t("dashboard.titles.scheduledintoday"),
|
||||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
|
||||||
}),
|
|
||||||
component: DashboardScheduledInToday,
|
component: DashboardScheduledInToday,
|
||||||
gqlFragment: DashboardScheduledInTodayGql,
|
gqlFragment: DashboardScheduledInTodayGql,
|
||||||
minW: 10,
|
minW: 6,
|
||||||
minH: 2,
|
minH: 2,
|
||||||
w: 10,
|
w: 10,
|
||||||
h: 2,
|
h: 3,
|
||||||
},
|
},
|
||||||
ScheduleOutToday: {
|
ScheduleOutToday: {
|
||||||
label: i18next.t("dashboard.titles.scheduledouttoday", {
|
label: i18next.t("dashboard.titles.scheduledouttoday"),
|
||||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
|
||||||
}),
|
|
||||||
component: DashboardScheduledOutToday,
|
component: DashboardScheduledOutToday,
|
||||||
gqlFragment: DashboardScheduledOutTodayGql,
|
gqlFragment: DashboardScheduledOutTodayGql,
|
||||||
minW: 10,
|
minW: 6,
|
||||||
minH: 2,
|
minH: 2,
|
||||||
w: 10,
|
w: 10,
|
||||||
h: 2,
|
h: 3,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -306,8 +302,7 @@ const createDashboardQuery = (state) => {
|
|||||||
.map((item, index) => componentList[item.i].gqlFragment || "")
|
.map((item, index) => componentList[item.i].gqlFragment || "")
|
||||||
.join("");
|
.join("");
|
||||||
return gql`
|
return gql`
|
||||||
query QUERY_DASHBOARD_DETAILS {
|
query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""}
|
||||||
${componentBasedAdditions || ""}
|
|
||||||
monthly_sales: jobs(where: {_and: [
|
monthly_sales: jobs(where: {_and: [
|
||||||
{ voided: {_eq: false}},
|
{ voided: {_eq: false}},
|
||||||
{date_invoiced: {_gte: "${moment()
|
{date_invoiced: {_gte: "${moment()
|
||||||
@@ -317,11 +312,11 @@ const createDashboardQuery = (state) => {
|
|||||||
.endOf("month")
|
.endOf("month")
|
||||||
.endOf("day")
|
.endOf("day")
|
||||||
.toISOString()}"}}]}) {
|
.toISOString()}"}}]}) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
date_invoiced
|
date_invoiced
|
||||||
job_totals
|
job_totals
|
||||||
rate_la1
|
rate_la1
|
||||||
rate_la2
|
rate_la2
|
||||||
rate_la3
|
rate_la3
|
||||||
rate_la4
|
rate_la4
|
||||||
@@ -344,43 +339,42 @@ const createDashboardQuery = (state) => {
|
|||||||
rate_mapa
|
rate_mapa
|
||||||
rate_mash
|
rate_mash
|
||||||
rate_matd
|
rate_matd
|
||||||
joblines(where: { removed: { _eq: false } }) {
|
joblines(where: { removed: { _eq: false } }) {
|
||||||
id
|
id
|
||||||
mod_lbr_ty
|
mod_lbr_ty
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
act_price
|
act_price
|
||||||
part_qty
|
part_qty
|
||||||
part_type
|
part_type
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
production_jobs: jobs(where: { inproduction: { _eq: true } }) {
|
||||||
production_jobs: jobs(where: { inproduction: { _eq: true } }) {
|
id
|
||||||
|
ro_number
|
||||||
|
ins_co_nm
|
||||||
|
job_totals
|
||||||
|
joblines(where: { removed: { _eq: false } }) {
|
||||||
id
|
id
|
||||||
ro_number
|
mod_lbr_ty
|
||||||
ins_co_nm
|
mod_lb_hrs
|
||||||
job_totals
|
act_price
|
||||||
joblines(where: { removed: { _eq: false } }) {
|
part_qty
|
||||||
id
|
part_type
|
||||||
mod_lbr_ty
|
}
|
||||||
mod_lb_hrs
|
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
||||||
act_price
|
aggregate {
|
||||||
part_qty
|
sum {
|
||||||
part_type
|
mod_lb_hrs
|
||||||
}
|
|
||||||
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
|
||||||
aggregate {
|
|
||||||
sum {
|
|
||||||
mod_lb_hrs
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
|
}
|
||||||
aggregate {
|
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
|
||||||
sum {
|
aggregate {
|
||||||
mod_lb_hrs
|
sum {
|
||||||
}
|
mod_lb_hrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -128,7 +128,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
height: 80%;
|
height: calc(100% - 2rem);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
// // background-color: red;
|
// // background-color: red;
|
||||||
// height: 90%;
|
// height: 90%;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -117,7 +118,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey={(record) => `${record.InvoiceNumber}${record.Account}`}
|
rowKey={(record) => `${record.InvoiceNumber}${record.Account}`}
|
||||||
dataSource={allocationsSummary}
|
dataSource={allocationsSummary}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -94,7 +95,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
|
|||||||
<Alert type="warning" message={t("jobs.labels.dms.disablebillwip")} />
|
<Alert type="warning" message={t("jobs.labels.dms.disablebillwip")} />
|
||||||
)}
|
)}
|
||||||
<Table
|
<Table
|
||||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="center"
|
rowKey="center"
|
||||||
dataSource={allocationsSummary}
|
dataSource={allocationsSummary}
|
||||||
|
|||||||
@@ -191,6 +191,13 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
|||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="dms_model_override"
|
||||||
|
label={t("jobs.fields.dms.dms_model_override")}
|
||||||
|
initialValue={false}
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export function EmailOverlayContainer({
|
|||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
],
|
],
|
||||||
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.src),
|
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.fullsize),
|
||||||
//attachments,
|
//attachments,
|
||||||
});
|
});
|
||||||
notification["success"]({ message: t("emails.successes.sent") });
|
notification["success"]({ message: t("emails.successes.sent") });
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Button, Col, Collapse, Result, Row, Space } from "antd";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { withTranslation } from "react-i18next";
|
import { withTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import {
|
import {
|
||||||
@@ -138,7 +138,6 @@ class ErrorBoundary extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(
|
export default Sentry.withErrorBoundary(
|
||||||
mapStateToProps,
|
connect(mapStateToProps, mapDispatchToProps)(withTranslation()(ErrorBoundary))
|
||||||
mapDispatchToProps
|
);
|
||||||
)(withTranslation()(ErrorBoundary));
|
|
||||||
|
|||||||
@@ -65,8 +65,17 @@ export function FormDatePicker({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_a.isValid() && onChange)
|
if (_a.isValid() && onChange) {
|
||||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
if (onlyFuture) {
|
||||||
|
if (moment().subtract(1, "day").isBefore(_a)) {
|
||||||
|
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||||
|
} else {
|
||||||
|
onChange(isDateOnly ? moment().format("YYYY-MM-DD") : moment());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { forwardRef } from "react";
|
import React, { forwardRef } from "react";
|
||||||
//import DatePicker from "react-datepicker";
|
//import DatePicker from "react-datepicker";
|
||||||
//import "react-datepicker/src/stylesheets/datepicker.scss";
|
//import "react-datepicker/src/stylesheets/datepicker.scss";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import { TimePicker } from "antd";
|
import { TimePicker } from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||||
//To be used as a form element only.
|
//To be used as a form element only.
|
||||||
|
|
||||||
const DateTimePicker = (
|
const DateTimePicker = (
|
||||||
@@ -26,20 +26,21 @@ const DateTimePicker = (
|
|||||||
value={value}
|
value={value}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
onlyFuture={onlyFuture}
|
||||||
isDateOnly={false}
|
isDateOnly={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TimePicker
|
<TimePicker
|
||||||
value={value ? moment(value) : null}
|
value={value ? moment(value) : null}
|
||||||
{...(onlyFuture && {
|
{...(onlyFuture && {
|
||||||
disabledDate: (d) => moment().isAfter(d),
|
disabledDate: (d) => moment().isAfter(d),
|
||||||
})}
|
})}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
showSecond={false}
|
showSecond={false}
|
||||||
minuteStep={15}
|
minuteStep={15}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
format="hh:mm a"
|
format="hh:mm a"
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export default function GlobalSearchOs() {
|
|||||||
job.v_make_desc || ""
|
job.v_make_desc || ""
|
||||||
} ${job.v_model_desc || ""}`}</span>
|
} ${job.v_model_desc || ""}`}</span>
|
||||||
<span>{`${job.clm_no || ""}`}</span>
|
<span>{`${job.clm_no || ""}`}</span>
|
||||||
|
<span>{`${job.plate_no || ""}`}</span>
|
||||||
</Space>
|
</Space>
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import Icon, {
|
|||||||
//GlobalOutlined,
|
//GlobalOutlined,
|
||||||
HomeFilled,
|
HomeFilled,
|
||||||
ImportOutlined,
|
ImportOutlined,
|
||||||
|
// InfoCircleOutlined,
|
||||||
LineChartOutlined,
|
LineChartOutlined,
|
||||||
PaperClipOutlined,
|
PaperClipOutlined,
|
||||||
PhoneOutlined,
|
PhoneOutlined,
|
||||||
@@ -26,8 +27,11 @@ import Icon, {
|
|||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Layout, Menu } from "antd";
|
import {
|
||||||
import React from "react";
|
Layout,
|
||||||
|
Menu, // Switch, Tooltip
|
||||||
|
} from "antd";
|
||||||
|
import React from "react"; //, { useEffect, useState }
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { BsKanban } from "react-icons/bs";
|
import { BsKanban } from "react-icons/bs";
|
||||||
import {
|
import {
|
||||||
@@ -52,6 +56,7 @@ import {
|
|||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
|
//import { handleBeta, setBeta, checkBeta } from "../../utils/handleBeta";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -102,9 +107,21 @@ function Header({
|
|||||||
{},
|
{},
|
||||||
bodyshop && bodyshop.imexshopid
|
bodyshop && bodyshop.imexshopid
|
||||||
);
|
);
|
||||||
|
//const [betaSwitch, setBetaSwitch] = useState(false);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const isBeta = checkBeta();
|
||||||
|
// setBetaSwitch(isBeta);
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// const betaSwitchChange = (checked) => {
|
||||||
|
// setBeta(checked);
|
||||||
|
// setBetaSwitch(checked);
|
||||||
|
// handleBeta();
|
||||||
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Header>
|
<Layout.Header>
|
||||||
<Menu
|
<Menu
|
||||||
@@ -430,6 +447,18 @@ function Header({
|
|||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
))}
|
))}
|
||||||
</Menu.SubMenu>
|
</Menu.SubMenu>
|
||||||
|
{
|
||||||
|
// <Menu.Item style={{marginLeft: 'auto'}} key="profile">
|
||||||
|
// <Tooltip title="A more modern ImEX Online is ready for you to try! You can switch back at any time.">
|
||||||
|
// <InfoCircleOutlined/>
|
||||||
|
// <span style={{marginRight: 8}}>Try the new ImEX Online</span>
|
||||||
|
// <Switch
|
||||||
|
// checked={betaSwitch}
|
||||||
|
// onChange={betaSwitchChange}
|
||||||
|
// />
|
||||||
|
// </Tooltip>
|
||||||
|
// </Menu.Item>
|
||||||
|
}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Layout.Header>
|
</Layout.Header>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component";
|
import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component";
|
||||||
import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component";
|
import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -213,7 +214,7 @@ export function JobsList({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{
|
pagination={{
|
||||||
position: "top",
|
position: "top",
|
||||||
pageSize: 25,
|
pageSize: pageLimit,
|
||||||
current: parseInt(page || 1),
|
current: parseInt(page || 1),
|
||||||
total: total,
|
total: total,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "../../redux/application/application.actions";
|
} from "../../redux/application/application.actions";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import InventoryListPaginated from "./inventory-list.component";
|
import InventoryListPaginated from "./inventory-list.component";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//bodyshop: selectBodyshop,
|
//bodyshop: selectBodyshop,
|
||||||
@@ -32,8 +33,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) {
|
|||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
variables: {
|
variables: {
|
||||||
search: search || "",
|
search: search || "",
|
||||||
offset: page ? (page - 1) * 25 : 0,
|
offset: page ? (page - 1) * pageLimit : 0,
|
||||||
limit: 25,
|
limit: pageLimit,
|
||||||
consumedIsNull: showall === "true" ? null : true,
|
consumedIsNull: showall === "true" ? null : true,
|
||||||
order: [
|
order: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Form,
|
Form,
|
||||||
|
Input,
|
||||||
Menu,
|
Menu,
|
||||||
notification,
|
notification,
|
||||||
Popover,
|
Popover,
|
||||||
@@ -34,6 +35,8 @@ import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.
|
|||||||
import ScheduleAtChange from "./job-at-change.component";
|
import ScheduleAtChange from "./job-at-change.component";
|
||||||
import ScheduleEventColor from "./schedule-event.color.component";
|
import ScheduleEventColor from "./schedule-event.color.component";
|
||||||
import ScheduleEventNote from "./schedule-event.note.component";
|
import ScheduleEventNote from "./schedule-event.note.component";
|
||||||
|
import { useMutation } from "@apollo/client";
|
||||||
|
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -58,16 +61,44 @@ export function ScheduleEventComponent({
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
|
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||||
|
const [title, setTitle] = useState(event.title);
|
||||||
const blockContent = (
|
const blockContent = (
|
||||||
<div>
|
<Space direction="vertical" wrap>
|
||||||
|
<Input
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.currentTarget.value)}
|
||||||
|
onBlur={async () => {
|
||||||
|
await updateAppointment({
|
||||||
|
variables: {
|
||||||
|
appid: event.id,
|
||||||
|
app: {
|
||||||
|
title: title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
optimisticResponse: {
|
||||||
|
update_appointments: {
|
||||||
|
__typename: "appointments_mutation_response",
|
||||||
|
returning: [
|
||||||
|
{
|
||||||
|
...event,
|
||||||
|
title: title,
|
||||||
|
__typename: "appointments",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleCancel({ id: event.id })}
|
onClick={() => handleCancel({ id: event.id })}
|
||||||
disabled={event.arrived}
|
disabled={event.arrived}
|
||||||
>
|
>
|
||||||
{t("appointments.actions.cancel")}
|
{t("appointments.actions.unblock")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|
||||||
const popoverContent = (
|
const popoverContent = (
|
||||||
|
|||||||
@@ -2,12 +2,16 @@ import { useMutation } from "@apollo/client";
|
|||||||
import { notification } from "antd";
|
import { notification } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries";
|
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import ScheduleEventComponent from "./schedule-event.component";
|
import ScheduleEventComponent from "./schedule-event.component";
|
||||||
|
|
||||||
export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
|
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
|
||||||
const [updateJob] = useMutation(UPDATE_JOB);
|
const [updateJob] = useMutation(UPDATE_JOB);
|
||||||
@@ -34,16 +38,24 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
|||||||
const jobUpdate = await updateJob({
|
const jobUpdate = await updateJob({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: event.job.id,
|
jobId: event.job.id,
|
||||||
|
|
||||||
job: {
|
job: {
|
||||||
date_scheduled: null,
|
date_scheduled: null,
|
||||||
scheduled_in: null,
|
scheduled_in: null,
|
||||||
scheduled_completion: null,
|
scheduled_completion: null,
|
||||||
lost_sale_reason,
|
lost_sale_reason,
|
||||||
|
date_lost_sale: new Date(),
|
||||||
status: bodyshop.md_ro_statuses.default_imported,
|
status: bodyshop.md_ro_statuses.default_imported,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (!jobUpdate.errors) {
|
||||||
|
dispatch(
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: event.job.id,
|
||||||
|
operation: AuditTrailMapping.appointmentcancel(lost_sale_reason),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
if (!!jobUpdate.errors) {
|
if (!!jobUpdate.errors) {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.updating", {
|
message: t("jobs.errors.updating", {
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ export default function JobBillsTotalComponent({
|
|||||||
const totalPartsSublet = Dinero(totals.parts.parts.total)
|
const totalPartsSublet = Dinero(totals.parts.parts.total)
|
||||||
.add(Dinero(totals.parts.sublets.total))
|
.add(Dinero(totals.parts.sublets.total))
|
||||||
.add(Dinero(totals.additional.shipping))
|
.add(Dinero(totals.additional.shipping))
|
||||||
.add(Dinero(totals.additional.towing));
|
.add(Dinero(totals.additional.towing))
|
||||||
|
.add(Dinero(totals.additional.additionalCosts));
|
||||||
|
|
||||||
const discrepancy = totalPartsSublet.subtract(billTotals);
|
const discrepancy = totalPartsSublet.subtract(billTotals);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Card, Form, Input, notification, Switch } from "antd";
|
import { Button, Card, Form, Input, notification, Switch } from "antd";
|
||||||
|
import moment from "moment-business-days";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -12,16 +13,15 @@ import {
|
|||||||
MARK_LATEST_APPOINTMENT_ARRIVED,
|
MARK_LATEST_APPOINTMENT_ARRIVED,
|
||||||
} from "../../../../graphql/appointments.queries";
|
} from "../../../../graphql/appointments.queries";
|
||||||
import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
|
||||||
|
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
|
||||||
|
import { insertAuditTrail } from "../../../../redux/application/application.actions";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../../../redux/user/user.selectors";
|
} from "../../../../redux/user/user.selectors";
|
||||||
|
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
|
||||||
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
|
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
|
||||||
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
|
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
|
||||||
import moment from "moment-business-days";
|
|
||||||
import { insertAuditTrail } from "../../../../redux/application/application.actions";
|
|
||||||
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
|
|
||||||
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -230,6 +230,7 @@ export function JobChecklistForm({
|
|||||||
)),
|
)),
|
||||||
scheduled_delivery:
|
scheduled_delivery:
|
||||||
job.scheduled_delivery && moment(job.scheduled_delivery),
|
job.scheduled_delivery && moment(job.scheduled_delivery),
|
||||||
|
production_vars: job.production_vars,
|
||||||
}),
|
}),
|
||||||
...(type === "deliver" && {
|
...(type === "deliver" && {
|
||||||
removeFromProduction: true,
|
removeFromProduction: true,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
|
import {pageLimit} from "../../utils/config";
|
||||||
export default function JobCostingPartsTable({ data, summaryData }) {
|
export default function JobCostingPartsTable({ data, summaryData }) {
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
@@ -98,7 +99,7 @@ export default function JobCostingPartsTable({ data, summaryData }) {
|
|||||||
x: "50%", //y: "40rem"
|
x: "50%", //y: "40rem"
|
||||||
}}
|
}}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={filteredData}
|
dataSource={filteredData}
|
||||||
|
|||||||
@@ -7,21 +7,31 @@ import { connect } from "react-redux";
|
|||||||
import { useHistory } from "react-router";
|
import { useHistory } from "react-router";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { UPDATE_JOB_LINES_IOU } from "../../graphql/jobs-lines.queries";
|
import { UPDATE_JOB_LINES_IOU } from "../../graphql/jobs-lines.queries";
|
||||||
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import { CreateIouForJob } from "../jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util";
|
import { CreateIouForJob } from "../jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
|
technician: selectTechnician,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobCreateIOU);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobCreateIOU);
|
||||||
|
|
||||||
export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines }) {
|
export function JobCreateIOU({
|
||||||
|
bodyshop,
|
||||||
|
currentUser,
|
||||||
|
job,
|
||||||
|
selectedJobLines,
|
||||||
|
technician,
|
||||||
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -79,13 +89,19 @@ export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines }) {
|
|||||||
title={t("jobs.labels.createiouwarning")}
|
title={t("jobs.labels.createiouwarning")}
|
||||||
onConfirm={handleCreateIou}
|
onConfirm={handleCreateIou}
|
||||||
disabled={
|
disabled={
|
||||||
!selectedJobLines || selectedJobLines.length === 0 || !job.converted
|
!selectedJobLines ||
|
||||||
|
selectedJobLines.length === 0 ||
|
||||||
|
!job.converted ||
|
||||||
|
technician
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={
|
disabled={
|
||||||
!selectedJobLines || selectedJobLines.length === 0 || !job.converted
|
!selectedJobLines ||
|
||||||
|
selectedJobLines.length === 0 ||
|
||||||
|
!job.converted ||
|
||||||
|
technician
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t("jobs.actions.createiou")}
|
{t("jobs.actions.createiou")}
|
||||||
|
|||||||
@@ -1,15 +1,37 @@
|
|||||||
import React from "react";
|
|
||||||
import { Card } from "antd";
|
import { Card } from "antd";
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
|
|
||||||
export default function JobDetailCardTemplate({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
technician: selectTechnician,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(JobDetailCardTemplate);
|
||||||
|
|
||||||
|
export function JobDetailCardTemplate({
|
||||||
loading,
|
loading,
|
||||||
title,
|
title,
|
||||||
extraLink,
|
extraLink,
|
||||||
|
technician,
|
||||||
...otherProps
|
...otherProps
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
let extra;
|
let extra;
|
||||||
if (extraLink) extra = { extra: <Link to={extraLink}>More</Link> };
|
if (extraLink && !technician)
|
||||||
|
extra = {
|
||||||
|
extra: <Link to={extraLink}>{t("jobs.labels.cards.more")}</Link>,
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
246
client/src/components/job-lifecycle/job-lifecycle.component.jsx
Normal file
246
client/src/components/job-lifecycle/job-lifecycle.component.jsx
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
import React, {useCallback, useEffect, useState} from 'react';
|
||||||
|
import moment from "moment";
|
||||||
|
import axios from 'axios';
|
||||||
|
import {Badge, Card, Space, Table, Tag} from 'antd';
|
||||||
|
import {gql, useQuery} from "@apollo/client";
|
||||||
|
import {DateTimeFormatterFunction} from "../../utils/DateFormatter";
|
||||||
|
import {isEmpty} from "lodash";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
|
require('./job-lifecycle.styles.scss');
|
||||||
|
|
||||||
|
// show text on bar if text can fit
|
||||||
|
export function JobLifecycleComponent({job, statuses, ...rest}) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [lifecycleData, setLifecycleData] = useState(null);
|
||||||
|
const {t} = useTranslation(); // Used for tracking external state changes.
|
||||||
|
|
||||||
|
const {data} = useQuery(gql`
|
||||||
|
query get_job_test($id: uuid!){
|
||||||
|
jobs_by_pk(id:$id){
|
||||||
|
id
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
variables: {
|
||||||
|
id: job.id
|
||||||
|
},
|
||||||
|
fetchPolicy: 'cache-only'
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the lifecycle data for the job.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const getLifecycleData = useCallback(async () => {
|
||||||
|
if (job && job.id && statuses && statuses.statuses) {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await axios.post("/job/lifecycle", {
|
||||||
|
jobids: job.id,
|
||||||
|
statuses: statuses.statuses
|
||||||
|
});
|
||||||
|
const data = response.data.transition[job.id];
|
||||||
|
setLifecycleData(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [job, statuses, t]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data) return;
|
||||||
|
setTimeout(() => {
|
||||||
|
getLifecycleData().catch(err => console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`));
|
||||||
|
}, 500);
|
||||||
|
}, [data, getLifecycleData, t]);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.value'),
|
||||||
|
dataIndex: 'value',
|
||||||
|
key: 'value',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.start'),
|
||||||
|
dataIndex: 'start',
|
||||||
|
key: 'start',
|
||||||
|
render: (text) => DateTimeFormatterFunction(text),
|
||||||
|
sorter: (a, b) => moment(a.start).unix() - moment(b.start).unix(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.relative_start'),
|
||||||
|
dataIndex: 'start_readable',
|
||||||
|
key: 'start_readable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.end'),
|
||||||
|
dataIndex: 'end',
|
||||||
|
key: 'end',
|
||||||
|
sorter: (a, b) => {
|
||||||
|
if (isEmpty(a.end) || isEmpty(b.end)) {
|
||||||
|
if (isEmpty(a.end) && isEmpty(b.end)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return isEmpty(a.end) ? 1 : -1;
|
||||||
|
}
|
||||||
|
return moment(a.end).unix() - moment(b.end).unix();
|
||||||
|
},
|
||||||
|
render: (text) => isEmpty(text) ? t('job_lifecycle.content.not_available') : DateTimeFormatterFunction(text)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.relative_end'),
|
||||||
|
dataIndex: 'end_readable',
|
||||||
|
key: 'end_readable',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('job_lifecycle.columns.duration'),
|
||||||
|
dataIndex: 'duration_readable',
|
||||||
|
key: 'duration_readable',
|
||||||
|
sorter: (a, b) => a.duration - b.duration,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card loading={loading} title={t('job_lifecycle.content.title')}>
|
||||||
|
{!loading ? (
|
||||||
|
lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? (
|
||||||
|
<Space direction='vertical' style={{width: '100%'}}>
|
||||||
|
<Card
|
||||||
|
type='inner'
|
||||||
|
title={(
|
||||||
|
<Space direction='horizontal' size='small'>
|
||||||
|
<Badge status='processing' count={lifecycleData.durations.totalStatuses}/>
|
||||||
|
{t('job_lifecycle.content.title_durations')}
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
)}
|
||||||
|
style={{width: '100%'}}
|
||||||
|
>
|
||||||
|
<div id="bar-container" style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
height: '100px',
|
||||||
|
textAlign: 'center',
|
||||||
|
borderRadius: '5px',
|
||||||
|
borderWidth: '5px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: '#f0f2f5',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0
|
||||||
|
}}>
|
||||||
|
{lifecycleData.durations.summations.map((key, index, array) => {
|
||||||
|
const isFirst = index === 0;
|
||||||
|
const isLast = index === array.length - 1;
|
||||||
|
return (
|
||||||
|
<div key={key.status} style={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
margin: 0,
|
||||||
|
padding: 0,
|
||||||
|
|
||||||
|
borderTop: '1px solid #f0f2f5',
|
||||||
|
borderBottom: '1px solid #f0f2f5',
|
||||||
|
borderLeft: isFirst ? '1px solid #f0f2f5' : undefined,
|
||||||
|
borderRight: isLast ? '1px solid #f0f2f5' : undefined,
|
||||||
|
|
||||||
|
backgroundColor: key.color,
|
||||||
|
width: `${key.percentage}%`
|
||||||
|
}}
|
||||||
|
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
|
||||||
|
>
|
||||||
|
|
||||||
|
{key.percentage > 15 ?
|
||||||
|
<>
|
||||||
|
<div>{key.roundedPercentage}</div>
|
||||||
|
<div style={{
|
||||||
|
backgroundColor: '#f0f2f5',
|
||||||
|
borderRadius: '5px',
|
||||||
|
paddingRight: '2px',
|
||||||
|
paddingLeft: '2px',
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
}}>
|
||||||
|
{key.status}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<Card type='inner' title={t('job_lifecycle.content.legend_title')}
|
||||||
|
style={{marginTop: '10px'}}>
|
||||||
|
<div>
|
||||||
|
{lifecycleData.durations.summations.map((key) => (
|
||||||
|
<Tag 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}`}
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#f0f2f5',
|
||||||
|
color: '#000',
|
||||||
|
padding: '4px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}}>
|
||||||
|
{key.status} ({key.roundedPercentage})
|
||||||
|
</div>
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
{(lifecycleData?.durations?.humanReadableTotal) ||
|
||||||
|
(lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable) ?
|
||||||
|
<Card style={{marginTop: '10px'}}>
|
||||||
|
<ul>
|
||||||
|
{lifecycleData.durations && lifecycleData.durations.humanReadableTotal &&
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
style={{fontWeight: 'bold'}}>{t('job_lifecycle.content.previous_status_accumulated_time')}:</span> {lifecycleData.durations.humanReadableTotal}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
{lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable &&
|
||||||
|
<li>
|
||||||
|
<span
|
||||||
|
style={{fontWeight: 'bold'}}>{t('job_lifecycle.content.current_status_accumulated_time')} ({lifecycleData.lifecycle[0].value}):</span> {lifecycleData.durations.totalCurrentStatusDuration.humanReadable}
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</Card>
|
||||||
|
: null}
|
||||||
|
</Card>
|
||||||
|
<Card type='inner' title={(
|
||||||
|
<>
|
||||||
|
<Space direction="horizontal" size="small">
|
||||||
|
<Badge status='processing' count={lifecycleData.lifecycle.length}/>
|
||||||
|
{t('job_lifecycle.content.title_transitions')}
|
||||||
|
</Space>
|
||||||
|
</>
|
||||||
|
)}>
|
||||||
|
<Table style={{
|
||||||
|
overflow: 'auto',
|
||||||
|
width: '100%',
|
||||||
|
}} columns={columns} dataSource={lifecycleData.lifecycle}/>
|
||||||
|
</Card>
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
<Card type='inner' style={{textAlign: 'center'}}>
|
||||||
|
{t('job_lifecycle.content.data_unavailable')}
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Card type='inner' title={t('job_lifecycle.content.title_loading')}>
|
||||||
|
{t('job_lifecycle.content.loading')}
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default JobLifecycleComponent;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { useMutation } from "@apollo/client";
|
||||||
import { notification, Select, Space } from "antd";
|
import { notification, Select, Space } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useMutation } from "@apollo/client";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -56,8 +56,10 @@ export function JobLineLocationPopup({ bodyshop, jobline, disabled }) {
|
|||||||
<LoadingSpinner loading={loading}>
|
<LoadingSpinner loading={loading}>
|
||||||
<Select
|
<Select
|
||||||
autoFocus
|
autoFocus
|
||||||
|
allowClear
|
||||||
dropdownMatchSelectWidth={100}
|
dropdownMatchSelectWidth={100}
|
||||||
value={location}
|
value={location}
|
||||||
|
onClear={() => setLocation(null)}
|
||||||
onSelect={handleChange}
|
onSelect={handleChange}
|
||||||
onBlur={handleSave}
|
onBlur={handleSave}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export default function JobLinesBillRefernece({ jobline }) {
|
|||||||
return (
|
return (
|
||||||
<div style={{ color: subletRequired && "tomato" }}>
|
<div style={{ color: subletRequired && "tomato" }}>
|
||||||
{subletRequired && <WarningFilled />}
|
{subletRequired && <WarningFilled />}
|
||||||
{`${(billLine.actual_price * billLine.quantity).toFixed(2)} (${
|
{`${billLine.actual_price.toFixed(2)} x ${billLine.quantity} (${
|
||||||
billLine.bill.vendor.name
|
billLine.bill.vendor.name
|
||||||
} #${billLine.bill.invoice_number})`}
|
} #${billLine.bill.invoice_number})`}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export default function JobReconciliationModalComponent({ job, bills }) {
|
|||||||
|
|
||||||
const jobLineData = job.joblines.filter(
|
const jobLineData = job.joblines.filter(
|
||||||
(j) =>
|
(j) =>
|
||||||
(j.part_type !== null && j.part_type !== "PAE") ||
|
(j.part_type !== "PAE" && j.act_price !== 0 && j.part_qty !== 0) ||
|
||||||
|
j.misc_amt !== 0 ||
|
||||||
(j.line_desc &&
|
(j.line_desc &&
|
||||||
j.line_desc.toLowerCase().includes("towing") &&
|
j.line_desc.toLowerCase().includes("towing") &&
|
||||||
j.lbr_op === "OP13") ||
|
j.lbr_op === "OP13") ||
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { useMutation, useLazyQuery } from "@apollo/client";
|
|
||||||
import { CheckCircleOutlined } from "@ant-design/icons";
|
import { CheckCircleOutlined } from "@ant-design/icons";
|
||||||
|
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Form,
|
Form,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
notification,
|
|
||||||
Popover,
|
Popover,
|
||||||
Space,
|
Space,
|
||||||
|
notification,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import {
|
import {
|
||||||
@@ -50,6 +50,7 @@ export default function ScoreboardAddButton({
|
|||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
logImEXEvent("job_close_add_to_scoreboard");
|
logImEXEvent("job_close_add_to_scoreboard");
|
||||||
|
values.date = moment(values.date).format("YYYY-MM-DD");
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
let result;
|
let result;
|
||||||
@@ -177,7 +178,7 @@ export default function ScoreboardAddButton({
|
|||||||
return acc + job.lbr_adjustments[val];
|
return acc + job.lbr_adjustments[val];
|
||||||
}, 0);
|
}, 0);
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
date: new moment(),
|
date: moment(),
|
||||||
bodyhrs: Math.round(v.bodyhrs * 10) / 10,
|
bodyhrs: Math.round(v.bodyhrs * 10) / 10,
|
||||||
painthrs: Math.round(v.painthrs * 10) / 10,
|
painthrs: Math.round(v.painthrs * 10) / 10,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ const JobSearchSelect = (
|
|||||||
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
|
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
|
||||||
|
|
||||||
const executeSearch = (v) => {
|
const executeSearch = (v) => {
|
||||||
if (v && v !== "") callSearch(v);
|
console.log(v);
|
||||||
|
if (v && v.variables?.search !== "" && v.variables.search.length >= 2)
|
||||||
|
callSearch(v);
|
||||||
};
|
};
|
||||||
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
||||||
|
|
||||||
|
|||||||
@@ -32,42 +32,105 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
|||||||
...(job.job_totals.totals.us_sales_tax_breakdown
|
...(job.job_totals.totals.us_sales_tax_breakdown
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key:
|
key: `${
|
||||||
bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 ||
|
bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 ||
|
||||||
"T1",
|
"T1"
|
||||||
|
} - ${[
|
||||||
|
job.cieca_pft.ty1_rate1,
|
||||||
|
job.cieca_pft.ty1_rate2,
|
||||||
|
job.cieca_pft.ty1_rate3,
|
||||||
|
job.cieca_pft.ty1_rate4,
|
||||||
|
job.cieca_pft.ty1_rate5,
|
||||||
|
]
|
||||||
|
.filter((i) => i > 0)
|
||||||
|
.join(", ")}%`,
|
||||||
total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax,
|
total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key:
|
key: `${
|
||||||
bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 ||
|
bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 ||
|
||||||
"T2",
|
"T2"
|
||||||
|
} - ${[
|
||||||
|
job.cieca_pft.ty2_rate1,
|
||||||
|
job.cieca_pft.ty2_rate2,
|
||||||
|
job.cieca_pft.ty2_rate3,
|
||||||
|
job.cieca_pft.ty2_rate4,
|
||||||
|
job.cieca_pft.ty2_rate5,
|
||||||
|
]
|
||||||
|
.filter((i) => i > 0)
|
||||||
|
.join(", ")}%`,
|
||||||
total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax,
|
total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key:
|
key: `${
|
||||||
bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 ||
|
bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 ||
|
||||||
"T3",
|
"T3"
|
||||||
|
} - ${[
|
||||||
|
job.cieca_pft.ty3_rate1,
|
||||||
|
job.cieca_pft.ty3_rate2,
|
||||||
|
job.cieca_pft.ty3_rate3,
|
||||||
|
job.cieca_pft.ty3_rate4,
|
||||||
|
job.cieca_pft.ty3_rate5,
|
||||||
|
]
|
||||||
|
.filter((i) => i > 0)
|
||||||
|
.join(", ")}%`,
|
||||||
total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax,
|
total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key:
|
key: `${
|
||||||
bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 ||
|
bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 ||
|
||||||
"T4",
|
"T4"
|
||||||
|
} - ${[
|
||||||
|
job.cieca_pft.ty4_rate1,
|
||||||
|
job.cieca_pft.ty4_rate2,
|
||||||
|
job.cieca_pft.ty4_rate3,
|
||||||
|
job.cieca_pft.ty4_rate4,
|
||||||
|
job.cieca_pft.ty4_rate5,
|
||||||
|
]
|
||||||
|
.filter((i) => i > 0)
|
||||||
|
.join(", ")}%`,
|
||||||
total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax,
|
total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key:
|
key: `${
|
||||||
bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 ||
|
bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 ||
|
||||||
"T5",
|
"TT"
|
||||||
|
} - ${[
|
||||||
|
job.cieca_pft.ty5_rate1,
|
||||||
|
job.cieca_pft.ty5_rate2,
|
||||||
|
job.cieca_pft.ty5_rate3,
|
||||||
|
job.cieca_pft.ty5_rate4,
|
||||||
|
job.cieca_pft.ty5_rate5,
|
||||||
|
]
|
||||||
|
.filter((i) => i > 0)
|
||||||
|
.join(", ")}%`,
|
||||||
total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax,
|
total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax,
|
||||||
},
|
},
|
||||||
]
|
{
|
||||||
|
key: t("jobs.labels.total_sales_tax"),
|
||||||
|
bold: true,
|
||||||
|
total: Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty1Tax)
|
||||||
|
.add(
|
||||||
|
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty2Tax)
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty3Tax)
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty4Tax)
|
||||||
|
)
|
||||||
|
.add(
|
||||||
|
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty5Tax)
|
||||||
|
).toJSON(),
|
||||||
|
},
|
||||||
|
].filter((item) => item.total.amount !== 0)
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
key: t("jobs.labels.state_tax_amt"),
|
key: t("jobs.labels.state_tax_amt"),
|
||||||
total: job.job_totals.totals.state_tax,
|
total: job.job_totals.totals.state_tax,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
|
|
||||||
{
|
{
|
||||||
key: t("jobs.labels.total_repairs"),
|
key: t("jobs.labels.total_repairs"),
|
||||||
total: job.job_totals.totals.total_repairs,
|
total: job.job_totals.totals.total_repairs,
|
||||||
@@ -101,7 +164,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
|||||||
bold: true,
|
bold: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [job.job_totals, t, bodyshop.md_responsibility_centers]);
|
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -53,12 +53,14 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus">
|
<>
|
||||||
<Button shape="round">
|
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus">
|
||||||
<span>{job.status}</span>
|
<Button shape="round">
|
||||||
|
<span>{job.status}</span>
|
||||||
|
|
||||||
<DownCircleFilled />
|
<DownCircleFilled />
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import { DateTimeFormat } from "./../../utils/DateFormatter";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -53,7 +54,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
|||||||
operation: AuditTrailMapping.admin_jobfieldchange(
|
operation: AuditTrailMapping.admin_jobfieldchange(
|
||||||
key,
|
key,
|
||||||
changedAuditFields[key] instanceof moment
|
changedAuditFields[key] instanceof moment
|
||||||
? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
|
? DateTimeFormat(changedAuditFields[key])
|
||||||
: changedAuditFields[key]
|
: changedAuditFields[key]
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -179,6 +180,12 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
|||||||
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
||||||
<DateTimePicker />
|
<DateTimePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.date_lost_sale")}
|
||||||
|
name="date_lost_sale"
|
||||||
|
>
|
||||||
|
<DateTimePicker />
|
||||||
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,18 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, notification } from "antd";
|
import { Button, Space, notification } from "antd";
|
||||||
import { gql } from "@apollo/client";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import {
|
||||||
|
DELETE_DELIVERY_CHECKLIST,
|
||||||
|
DELETE_INTAKE_CHECKLIST,
|
||||||
|
} from "../../graphql/jobs.queries";
|
||||||
|
|
||||||
export default function JobAdminDeleteIntake({ job }) {
|
export default function JobAdminDeleteIntake({ job }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [deleteIntake] = useMutation(gql`
|
|
||||||
mutation DELETE_INTAKE($jobId: uuid!) {
|
|
||||||
update_jobs_by_pk(
|
|
||||||
pk_columns: { id: $jobId }
|
|
||||||
_set: { intakechecklist: null }
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
intakechecklist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const [DELETE_DELIVERY] = useMutation(gql`
|
const [deleteIntake] = useMutation(DELETE_INTAKE_CHECKLIST);
|
||||||
mutation DELETE_DELIVERY($jobId: uuid!) {
|
const [deleteDelivery] = useMutation(DELETE_DELIVERY_CHECKLIST);
|
||||||
update_jobs_by_pk(
|
|
||||||
pk_columns: { id: $jobId }
|
|
||||||
_set: { deliverchecklist: null }
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
deliverchecklist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const handleDelete = async (values) => {
|
const handleDelete = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -50,7 +34,7 @@ export default function JobAdminDeleteIntake({ job }) {
|
|||||||
|
|
||||||
const handleDeleteDelivery = async (values) => {
|
const handleDeleteDelivery = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await DELETE_DELIVERY({
|
const result = await deleteDelivery({
|
||||||
variables: { jobId: job.id },
|
variables: { jobId: job.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,12 +52,22 @@ export default function JobAdminDeleteIntake({ job }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button loading={loading} onClick={handleDelete}>
|
<Space wrap>
|
||||||
{t("jobs.labels.deleteintake")}
|
<Button
|
||||||
</Button>
|
loading={loading}
|
||||||
<Button loading={loading} onClick={handleDeleteDelivery}>
|
onClick={handleDelete}
|
||||||
{t("jobs.labels.deletedelivery")}
|
disabled={!job.intakechecklist}
|
||||||
</Button>
|
>
|
||||||
|
{t("jobs.labels.deleteintake")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
loading={loading}
|
||||||
|
onClick={handleDeleteDelivery}
|
||||||
|
disabled={!job.deliverychecklist}
|
||||||
|
>
|
||||||
|
{t("jobs.labels.deletedelivery")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, notification } from "antd";
|
import { Button, Space, notification } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@@ -7,6 +7,11 @@ import moment from "moment";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
|
import {
|
||||||
|
MARK_JOB_AS_EXPORTED,
|
||||||
|
MARK_JOB_AS_UNINVOICED,
|
||||||
|
MARK_JOB_FOR_REEXPORT,
|
||||||
|
} from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
@@ -35,58 +40,18 @@ export function JobAdminMarkReexport({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
const [markJobForReexport] = useMutation(gql`
|
|
||||||
mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) {
|
|
||||||
update_jobs_by_pk(
|
|
||||||
pk_columns: { id: $jobId }
|
|
||||||
_set: { date_exported: null
|
|
||||||
status: "${bodyshop.md_ro_statuses.default_invoiced}"
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
date_exported
|
|
||||||
status
|
|
||||||
date_invoiced
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const [markJobExported] = useMutation(gql`
|
const [markJobForReexport] = useMutation(MARK_JOB_FOR_REEXPORT);
|
||||||
mutation MARK_JOB_AS_EXPORTED($jobId: uuid!, $date_exported: timestamptz!) {
|
const [markJobExported] = useMutation(MARK_JOB_AS_EXPORTED);
|
||||||
update_jobs_by_pk(
|
const [markJobUninvoiced] = useMutation(MARK_JOB_AS_UNINVOICED);
|
||||||
pk_columns: { id: $jobId }
|
|
||||||
_set: { date_exported: $date_exported
|
|
||||||
status: "${bodyshop.md_ro_statuses.default_exported}"
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
date_exported
|
|
||||||
date_invoiced
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
const [markJobUninvoiced] = useMutation(gql`
|
|
||||||
mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, ) {
|
|
||||||
update_jobs_by_pk(
|
|
||||||
pk_columns: { id: $jobId }
|
|
||||||
_set: { date_exported: null
|
|
||||||
date_invoiced: null
|
|
||||||
status: "${bodyshop.md_ro_statuses.default_delivered}"
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
id
|
|
||||||
date_exported
|
|
||||||
date_invoiced
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const handleMarkForExport = async () => {
|
const handleMarkForExport = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await markJobForReexport({
|
const result = await markJobForReexport({
|
||||||
variables: { jobId: job.id },
|
variables: {
|
||||||
|
jobId: job.id,
|
||||||
|
default_invoiced: bodyshop.md_ro_statuses.default_invoiced,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
@@ -108,7 +73,11 @@ export function JobAdminMarkReexport({
|
|||||||
const handleMarkExported = async () => {
|
const handleMarkExported = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await markJobExported({
|
const result = await markJobExported({
|
||||||
variables: { jobId: job.id, date_exported: moment() },
|
variables: {
|
||||||
|
jobId: job.id,
|
||||||
|
date_exported: moment(),
|
||||||
|
default_exported: bodyshop.md_ro_statuses.default_exported,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await insertExportLog({
|
await insertExportLog({
|
||||||
@@ -144,7 +113,10 @@ export function JobAdminMarkReexport({
|
|||||||
const handleUninvoice = async () => {
|
const handleUninvoice = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await markJobUninvoiced({
|
const result = await markJobUninvoiced({
|
||||||
variables: { jobId: job.id },
|
variables: {
|
||||||
|
jobId: job.id,
|
||||||
|
default_delivered: bodyshop.md_ro_statuses.default_delivered,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
@@ -165,27 +137,29 @@ export function JobAdminMarkReexport({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Space wrap>
|
||||||
loading={loading}
|
<Button
|
||||||
disabled={!job.date_exported}
|
loading={loading}
|
||||||
onClick={handleMarkForExport}
|
disabled={!job.date_exported}
|
||||||
>
|
onClick={handleMarkForExport}
|
||||||
{t("jobs.labels.markforreexport")}
|
>
|
||||||
</Button>
|
{t("jobs.labels.markforreexport")}
|
||||||
<Button
|
</Button>
|
||||||
loading={loading}
|
<Button
|
||||||
disabled={job.date_exported}
|
loading={loading}
|
||||||
onClick={handleMarkExported}
|
disabled={job.date_exported}
|
||||||
>
|
onClick={handleMarkExported}
|
||||||
{t("jobs.actions.markasexported")}
|
>
|
||||||
</Button>
|
{t("jobs.actions.markasexported")}
|
||||||
<Button
|
</Button>
|
||||||
loading={loading}
|
<Button
|
||||||
disabled={!job.date_invoiced || job.date_exported}
|
loading={loading}
|
||||||
onClick={handleUninvoice}
|
disabled={!job.date_invoiced || job.date_exported}
|
||||||
>
|
onClick={handleUninvoice}
|
||||||
{t("jobs.actions.uninvoice")}
|
>
|
||||||
</Button>
|
{t("jobs.actions.uninvoice")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useMutation } from "@apollo/client";
|
||||||
|
import { Switch, notification } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { UPDATE_REMOVE_FROM_AR } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
insertAuditTrail: ({ jobid, operation }) =>
|
||||||
|
dispatch(insertAuditTrail({ jobid, operation })),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR);
|
||||||
|
|
||||||
|
export function JobsAdminRemoveAR({ insertAuditTrail, job }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [switchValue, setSwitchValue] = useState(job.remove_from_ar);
|
||||||
|
|
||||||
|
const [mutationUpdateRemoveFromAR] = useMutation(UPDATE_REMOVE_FROM_AR);
|
||||||
|
|
||||||
|
const handleChange = async (value) => {
|
||||||
|
setLoading(true);
|
||||||
|
const result = await mutationUpdateRemoveFromAR({
|
||||||
|
variables: { jobId: job.id, remove_from_ar: value },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.errors) {
|
||||||
|
notification["success"]({ message: t("jobs.successes.save") });
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.admin_job_remove_from_ar(value),
|
||||||
|
});
|
||||||
|
setSwitchValue(value);
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.saving", {
|
||||||
|
error: JSON.stringify(result.errors),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<div style={{ marginRight: "10px" }}>
|
||||||
|
{t("jobs.labels.remove_from_ar")}:
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Switch
|
||||||
|
checked={switchValue}
|
||||||
|
loading={loading}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, notification } from "antd";
|
import { Button, notification } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { UNVOID_JOB } from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
@@ -29,66 +30,17 @@ export function JobsAdminUnvoid({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [updateJob] = useMutation(gql`
|
const [mutationUnvoidJob] = useMutation(UNVOID_JOB);
|
||||||
mutation UNVOID_JOB($jobId: uuid!) {
|
|
||||||
update_jobs_by_pk(pk_columns: {id: $jobId}, _set: {voided: false, status: "${
|
|
||||||
bodyshop.md_ro_statuses.default_imported
|
|
||||||
}", date_void: null}) {
|
|
||||||
id
|
|
||||||
date_void
|
|
||||||
voided
|
|
||||||
status
|
|
||||||
}
|
|
||||||
insert_notes(objects: {jobid: $jobId, audit: true, created_by: "${
|
|
||||||
currentUser.email
|
|
||||||
}", text: "${t("jobs.labels.unvoidnote")}"}) {
|
|
||||||
returning {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
`);
|
|
||||||
|
|
||||||
// const result = await voidJob({
|
|
||||||
// variables: {
|
|
||||||
// jobId: job.id,
|
|
||||||
// job: {
|
|
||||||
// status: bodyshop.md_ro_statuses.default_void,
|
|
||||||
// voided: true,
|
|
||||||
// },
|
|
||||||
// note: [
|
|
||||||
// {
|
|
||||||
// jobid: job.id,
|
|
||||||
// created_by: currentUser.email,
|
|
||||||
// audit: true,
|
|
||||||
// text: t("jobs.labels.voidnote", {
|
|
||||||
// date: moment().format("MM/DD/yyy"),
|
|
||||||
// time: moment().format("hh:mm a"),
|
|
||||||
// }),
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (!!!result.errors) {
|
|
||||||
// notification["success"]({
|
|
||||||
// message: t("jobs.successes.voided"),
|
|
||||||
// });
|
|
||||||
// //go back to jobs list.
|
|
||||||
// history.push(`/manage/`);
|
|
||||||
// } else {
|
|
||||||
// notification["error"]({
|
|
||||||
// message: t("jobs.errors.voiding", {
|
|
||||||
// error: JSON.stringify(result.errors),
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
const handleUpdate = async (values) => {
|
const handleUpdate = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await updateJob({
|
const result = await mutationUnvoidJob({
|
||||||
variables: { jobId: job.id },
|
variables: {
|
||||||
|
jobId: job.id,
|
||||||
|
default_imported: bodyshop.md_ro_statuses.default_imported,
|
||||||
|
currentUserEmail: currentUser.email,
|
||||||
|
text: t("jobs.labels.unvoidnote"),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
@@ -110,8 +62,10 @@ mutation UNVOID_JOB($jobId: uuid!) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button loading={loading} disabled={!job.voided} onClick={handleUpdate}>
|
<>
|
||||||
{t("jobs.actions.unvoid")}
|
<Button loading={loading} disabled={!job.voided} onClick={handleUpdate}>
|
||||||
</Button>
|
{t("jobs.actions.unvoid")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries";
|
|
||||||
import { gql } from "@apollo/client";
|
import { gql } from "@apollo/client";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||||
|
|
||||||
export const GetSupplementDelta = async (client, jobId, newLines) => {
|
export const GetSupplementDelta = async (client, jobId, newLines) => {
|
||||||
const {
|
const {
|
||||||
@@ -50,6 +50,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
|
|||||||
.reduce((acc, value, idx) => {
|
.reduce((acc, value, idx) => {
|
||||||
return acc + generateRemoveQuery(value, idx);
|
return acc + generateRemoveQuery(value, idx);
|
||||||
}, "");
|
}, "");
|
||||||
|
//console.log(insertQueries, updateQueries, removeQueries);
|
||||||
|
|
||||||
if ((insertQueries + updateQueries + removeQueries).trim() === "") {
|
if ((insertQueries + updateQueries + removeQueries).trim() === "") {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ const headerFields = [
|
|||||||
"ins_ct_ph",
|
"ins_ct_ph",
|
||||||
"ins_ct_phx",
|
"ins_ct_phx",
|
||||||
"loss_cat",
|
"loss_cat",
|
||||||
//ad2
|
//AD2
|
||||||
"clmt_ln",
|
"clmt_ln",
|
||||||
"clmt_fn",
|
"clmt_fn",
|
||||||
"clmt_title",
|
"clmt_title",
|
||||||
@@ -219,7 +219,16 @@ const headerFields = [
|
|||||||
"loc_title",
|
"loc_title",
|
||||||
"loc_ph",
|
"loc_ph",
|
||||||
"loc_phx",
|
"loc_phx",
|
||||||
"loc_ea"
|
"loc_ea",
|
||||||
|
//VEH
|
||||||
|
"plate_no",
|
||||||
|
"plate_st",
|
||||||
|
"v_vin",
|
||||||
|
"v_model_yr",
|
||||||
|
"v_make_desc",
|
||||||
|
"v_model_desc",
|
||||||
|
"v_options",
|
||||||
|
"v_color",
|
||||||
];
|
];
|
||||||
|
|
||||||
export default headerFields;
|
export default headerFields;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useTreatments } from "@splitsoftware/splitio-react";
|
|||||||
import { Button, Col, Row, notification } from "antd";
|
import { Button, Col, Row, notification } from "antd";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import moment from "moment";
|
import moment from "moment-business-days";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
//import confirmDialog from "../../utils/asyncConfirm";
|
||||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
|
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
|
||||||
@@ -72,6 +73,16 @@ export function JobsAvailableContainer({
|
|||||||
|
|
||||||
const [selectedJob, setSelectedJob] = useState(null);
|
const [selectedJob, setSelectedJob] = useState(null);
|
||||||
const [selectedOwner, setSelectedOwner] = useState(null);
|
const [selectedOwner, setSelectedOwner] = useState(null);
|
||||||
|
const [partsQueueToggle, setPartsQueueToggle] = useState(
|
||||||
|
bodyshop.md_functionality_toggles.parts_queue_toggle
|
||||||
|
);
|
||||||
|
const [updateSchComp, setSchComp] = useState({
|
||||||
|
actual_in: moment(),
|
||||||
|
checked: false,
|
||||||
|
scheduled_completion: moment(),
|
||||||
|
automatic: false,
|
||||||
|
});
|
||||||
|
|
||||||
const [insertLoading, setInsertLoading] = useState(false);
|
const [insertLoading, setInsertLoading] = useState(false);
|
||||||
|
|
||||||
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
||||||
@@ -93,6 +104,7 @@ export function JobsAvailableContainer({
|
|||||||
logImEXEvent("job_import_new");
|
logImEXEvent("job_import_new");
|
||||||
|
|
||||||
setOwnerModalVisible(false);
|
setOwnerModalVisible(false);
|
||||||
|
|
||||||
setInsertLoading(true);
|
setInsertLoading(true);
|
||||||
const estData = replaceEmpty(
|
const estData = replaceEmpty(
|
||||||
lazyData?.available_jobs_by_pk || estDataRaw.data.available_jobs_by_pk
|
lazyData?.available_jobs_by_pk || estDataRaw.data.available_jobs_by_pk
|
||||||
@@ -124,7 +136,7 @@ export function JobsAvailableContainer({
|
|||||||
|
|
||||||
let existingVehicles;
|
let existingVehicles;
|
||||||
if (estData.est_data.v_vin) {
|
if (estData.est_data.v_vin) {
|
||||||
//There's vehicle data, need to double check the VIN.
|
//There's vehicle data, need to double-check the VIN.
|
||||||
existingVehicles = await client.query({
|
existingVehicles = await client.query({
|
||||||
query: SEARCH_VEHICLE_BY_VIN,
|
query: SEARCH_VEHICLE_BY_VIN,
|
||||||
variables: {
|
variables: {
|
||||||
@@ -147,7 +159,7 @@ export function JobsAvailableContainer({
|
|||||||
text: t("jobs.labels.importnote"),
|
text: t("jobs.labels.importnote"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
queued_for_parts: true,
|
queued_for_parts: partsQueueToggle,
|
||||||
...(existingVehicles && existingVehicles.data.vehicles.length > 0
|
...(existingVehicles && existingVehicles.data.vehicles.length > 0
|
||||||
? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null }
|
? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null }
|
||||||
: {}),
|
: {}),
|
||||||
@@ -197,17 +209,27 @@ export function JobsAvailableContainer({
|
|||||||
refetch();
|
refetch();
|
||||||
setInsertLoading(false);
|
setInsertLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
} catch (r) {
|
} catch (r) {
|
||||||
//error while inserting
|
//error while inserting
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.creating", { error: r.message }),
|
message: t("jobs.errors.creating", { error: r.message }),
|
||||||
});
|
});
|
||||||
refetch();
|
refetch().catch((err) => {
|
||||||
|
console.error(
|
||||||
|
`Something went wrong in jobs available table container - ${
|
||||||
|
err.message || ""
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
});
|
||||||
setInsertLoading(false);
|
setInsertLoading(false);
|
||||||
|
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//Suplement scenario
|
//Supplement scenario
|
||||||
const onJobFindModalOk = async () => {
|
const onJobFindModalOk = async () => {
|
||||||
logImEXEvent("job_import_supplement");
|
logImEXEvent("job_import_supplement");
|
||||||
|
|
||||||
@@ -229,6 +251,22 @@ export function JobsAvailableContainer({
|
|||||||
await CheckTaxRates(supp, bodyshop);
|
await CheckTaxRates(supp, bodyshop);
|
||||||
await ResolveCCCLineIssues(supp, bodyshop);
|
await ResolveCCCLineIssues(supp, bodyshop);
|
||||||
|
|
||||||
|
if (updateSchComp.checked === true) {
|
||||||
|
if (updateSchComp.automatic === true) {
|
||||||
|
const job_hrs = supp.joblines.data.reduce(
|
||||||
|
(acc, val) => acc + val.mod_lb_hrs,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const num_days = job_hrs / bodyshop.target_touchtime;
|
||||||
|
supp.actual_in = updateSchComp.actual_in;
|
||||||
|
supp.scheduled_completion = moment(
|
||||||
|
updateSchComp.actual_in
|
||||||
|
).businessAdd(num_days, "days");
|
||||||
|
} else {
|
||||||
|
supp.scheduled_completion = updateSchComp.scheduled_completion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delete supp.owner;
|
delete supp.owner;
|
||||||
delete supp.vehicle;
|
delete supp.vehicle;
|
||||||
delete supp.ins_co_nm;
|
delete supp.ins_co_nm;
|
||||||
@@ -260,10 +298,13 @@ export function JobsAvailableContainer({
|
|||||||
// "0.00"
|
// "0.00"
|
||||||
// ),
|
// ),
|
||||||
// job_totals: newTotals,
|
// job_totals: newTotals,
|
||||||
// queued_for_parts: true,
|
queued_for_parts: partsQueueToggle,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
|
|
||||||
if (CriticalPartsScanning.treatment === "on") {
|
if (CriticalPartsScanning.treatment === "on") {
|
||||||
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
|
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
|
||||||
}
|
}
|
||||||
@@ -339,12 +380,14 @@ export function JobsAvailableContainer({
|
|||||||
const onOwnerModalCancel = () => {
|
const onOwnerModalCancel = () => {
|
||||||
setOwnerModalVisible(false);
|
setOwnerModalVisible(false);
|
||||||
setSelectedOwner(null);
|
setSelectedOwner(null);
|
||||||
|
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onJobModalCancel = () => {
|
const onJobModalCancel = () => {
|
||||||
setJobModalVisible(false);
|
setJobModalVisible(false);
|
||||||
modalSearchState[1]("");
|
modalSearchState[1]("");
|
||||||
setSelectedJob(null);
|
setSelectedJob(null);
|
||||||
|
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addJobAsNew = (record) => {
|
const addJobAsNew = (record) => {
|
||||||
@@ -365,6 +408,7 @@ export function JobsAvailableContainer({
|
|||||||
}, [addJobAsSupp, availableJobId, clm_no]);
|
}, [addJobAsSupp, availableJobId, clm_no]);
|
||||||
|
|
||||||
if (error) return <AlertComponent type="error" message={error.message} />;
|
if (error) return <AlertComponent type="error" message={error.message} />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoadingSpinner
|
<LoadingSpinner
|
||||||
loading={insertLoading}
|
loading={insertLoading}
|
||||||
@@ -374,6 +418,8 @@ export function JobsAvailableContainer({
|
|||||||
loading={estDataRaw.loading}
|
loading={estDataRaw.loading}
|
||||||
error={estDataRaw.error}
|
error={estDataRaw.error}
|
||||||
owner={owner}
|
owner={owner}
|
||||||
|
partsQueueToggle={partsQueueToggle}
|
||||||
|
setPartsQueueToggle={setPartsQueueToggle}
|
||||||
selectedOwner={selectedOwner}
|
selectedOwner={selectedOwner}
|
||||||
setSelectedOwner={setSelectedOwner}
|
setSelectedOwner={setSelectedOwner}
|
||||||
visible={ownerModalVisible}
|
visible={ownerModalVisible}
|
||||||
@@ -390,6 +436,10 @@ export function JobsAvailableContainer({
|
|||||||
onOk={onJobFindModalOk}
|
onOk={onJobFindModalOk}
|
||||||
onCancel={onJobModalCancel}
|
onCancel={onJobModalCancel}
|
||||||
modalSearchState={modalSearchState}
|
modalSearchState={modalSearchState}
|
||||||
|
partsQueueToggle={partsQueueToggle}
|
||||||
|
setPartsQueueToggle={setPartsQueueToggle}
|
||||||
|
updateSchComp={updateSchComp}
|
||||||
|
setSchComp={setSchComp}
|
||||||
/>
|
/>
|
||||||
{currentUser.email.includes("@rome.") ||
|
{currentUser.email.includes("@rome.") ||
|
||||||
currentUser.email.includes("@imex.") ? (
|
currentUser.email.includes("@imex.") ? (
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import client from "../../utils/GraphQLClient";
|
import client from "../../utils/GraphQLClient";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -20,6 +22,11 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
insertAuditTrail: ({ jobid, operation }) =>
|
||||||
|
dispatch(insertAuditTrail({ jobid, operation })),
|
||||||
|
});
|
||||||
|
|
||||||
function updateJobCache(items) {
|
function updateJobCache(items) {
|
||||||
client.cache.modify({
|
client.cache.modify({
|
||||||
id: "ROOT_QUERY",
|
id: "ROOT_QUERY",
|
||||||
@@ -40,6 +47,7 @@ export function JobsCloseExportButton({
|
|||||||
disabled,
|
disabled,
|
||||||
setSelectedJobs,
|
setSelectedJobs,
|
||||||
refetch,
|
refetch,
|
||||||
|
insertAuditTrail,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -181,6 +189,10 @@ export function JobsCloseExportButton({
|
|||||||
key: "jobsuccessexport",
|
key: "jobsuccessexport",
|
||||||
message: t("jobs.successes.exported"),
|
message: t("jobs.successes.exported"),
|
||||||
});
|
});
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: jobId,
|
||||||
|
operation: AuditTrailMapping.jobexported(),
|
||||||
|
});
|
||||||
updateJobCache(
|
updateJobCache(
|
||||||
jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)
|
jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)
|
||||||
);
|
);
|
||||||
@@ -192,12 +204,20 @@ export function JobsCloseExportButton({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
|
if (
|
||||||
|
bodyshop.accountingconfig &&
|
||||||
|
bodyshop.accountingconfig.qbo &&
|
||||||
|
successfulTransactions.length > 0
|
||||||
|
) {
|
||||||
notification.open({
|
notification.open({
|
||||||
type: "success",
|
type: "success",
|
||||||
key: "jobsuccessexport",
|
key: "jobsuccessexport",
|
||||||
message: t("jobs.successes.exported"),
|
message: t("jobs.successes.exported"),
|
||||||
});
|
});
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: jobId,
|
||||||
|
operation: AuditTrailMapping.jobexported(),
|
||||||
|
});
|
||||||
updateJobCache([
|
updateJobCache([
|
||||||
...new Set(
|
...new Set(
|
||||||
successfulTransactions.map(
|
successfulTransactions.map(
|
||||||
@@ -227,4 +247,7 @@ export function JobsCloseExportButton({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(JobsCloseExportButton);
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(JobsCloseExportButton);
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export function JobsConvertButton({
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select>
|
<Select showSearch>
|
||||||
{bodyshop.md_ins_cos.map((s, i) => (
|
{bodyshop.md_ins_cos.map((s, i) => (
|
||||||
<Select.Option key={i} value={s.name}>
|
<Select.Option key={i} value={s.name}>
|
||||||
{s.name}
|
{s.name}
|
||||||
|
|||||||
@@ -19,10 +19,13 @@ export default function JobsCreateOwnerInfoNewComponent() {
|
|||||||
label={t("owners.fields.ownr_ln")}
|
label={t("owners.fields.ownr_ln")}
|
||||||
name={["owner", "data", "ownr_ln"]}
|
name={["owner", "data", "ownr_ln"]}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
({ getFieldValue }) => ({
|
||||||
required: state.owner.new,
|
required:
|
||||||
|
state.owner.new &&
|
||||||
|
(!getFieldValue(["owner", "data", "ownr_co_nm"]) ||
|
||||||
|
getFieldValue(["owner", "data", "ownr_co_nm"]) === ""),
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
},
|
}),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input disabled={!state.owner.new} />
|
<Input disabled={!state.owner.new} />
|
||||||
@@ -31,10 +34,13 @@ export default function JobsCreateOwnerInfoNewComponent() {
|
|||||||
label={t("owners.fields.ownr_fn")}
|
label={t("owners.fields.ownr_fn")}
|
||||||
name={["owner", "data", "ownr_fn"]}
|
name={["owner", "data", "ownr_fn"]}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
({ getFieldValue }) => ({
|
||||||
required: state.owner.new,
|
required:
|
||||||
|
state.owner.new &&
|
||||||
|
(!getFieldValue(["owner", "data", "ownr_co_nm"]) ||
|
||||||
|
getFieldValue(["owner", "data", "ownr_co_nm"]) === ""),
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
},
|
}),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input disabled={!state.owner.new} />
|
<Input disabled={!state.owner.new} />
|
||||||
@@ -51,6 +57,17 @@ export default function JobsCreateOwnerInfoNewComponent() {
|
|||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("owners.fields.ownr_co_nm")}
|
label={t("owners.fields.ownr_co_nm")}
|
||||||
name={["owner", "data", "ownr_co_nm"]}
|
name={["owner", "data", "ownr_co_nm"]}
|
||||||
|
rules={[
|
||||||
|
({ getFieldValue }) => ({
|
||||||
|
required:
|
||||||
|
state.owner.new &&
|
||||||
|
(!getFieldValue(["owner", "data", "ownr_ln"]) ||
|
||||||
|
!getFieldValue(["owner", "data", "ownr_fn"]) ||
|
||||||
|
getFieldValue(["owner", "data", "ownr_ln"]) === "" ||
|
||||||
|
getFieldValue(["owner", "data", "ownr_fn"]) === ""),
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}),
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Input disabled={!state.owner.new} />
|
<Input disabled={!state.owner.new} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -145,6 +145,13 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
||||||
<DateTimePicker disabled={true || jobRO} />
|
<DateTimePicker disabled={true || jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.date_lost_sale")}
|
||||||
|
name="date_lost_sale"
|
||||||
|
>
|
||||||
|
<DateTimePicker disabled={true || jobRO} />
|
||||||
|
</Form.Item>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,12 +18,14 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.queries";
|
import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.queries";
|
||||||
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.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 { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
|
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
|
||||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||||
@@ -52,6 +54,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
|
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
|
||||||
setCardPaymentContext: (context) =>
|
setCardPaymentContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||||
|
insertAuditTrail: ({ jobid, operation }) =>
|
||||||
|
dispatch(insertAuditTrail({ jobid, operation })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobsDetailHeaderActions({
|
export function JobsDetailHeaderActions({
|
||||||
@@ -67,6 +71,7 @@ export function JobsDetailHeaderActions({
|
|||||||
setTimeTicketContext,
|
setTimeTicketContext,
|
||||||
setTimeTicketTaskContext,
|
setTimeTicketTaskContext,
|
||||||
setCardPaymentContext,
|
setCardPaymentContext,
|
||||||
|
insertAuditTrail,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -106,6 +111,14 @@ export function JobsDetailHeaderActions({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.alertToggle(
|
||||||
|
!!job.production_vars && !!job.production_vars.alert
|
||||||
|
? !job.production_vars.alert
|
||||||
|
: true
|
||||||
|
),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSuspend = (e) => {
|
const handleSuspend = (e) => {
|
||||||
@@ -119,6 +132,12 @@ export function JobsDetailHeaderActions({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.jobsuspend(
|
||||||
|
!!job.suspended ? !job.suspended : true
|
||||||
|
),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusmenu = (
|
const statusmenu = (
|
||||||
@@ -161,6 +180,7 @@ export function JobsDetailHeaderActions({
|
|||||||
scheduled_in: null,
|
scheduled_in: null,
|
||||||
scheduled_completion: null,
|
scheduled_completion: null,
|
||||||
lost_sale_reason,
|
lost_sale_reason,
|
||||||
|
date_lost_sale: new Date(),
|
||||||
status: bodyshop.md_ro_statuses.default_imported,
|
status: bodyshop.md_ro_statuses.default_imported,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -169,6 +189,11 @@ export function JobsDetailHeaderActions({
|
|||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("appointments.successes.canceled"),
|
message: t("appointments.successes.canceled"),
|
||||||
});
|
});
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation:
|
||||||
|
AuditTrailMapping.appointmentcancel(lost_sale_reason),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -542,6 +567,10 @@ export function JobsDetailHeaderActions({
|
|||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("jobs.successes.voided"),
|
message: t("jobs.successes.voided"),
|
||||||
});
|
});
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.jobvoid(),
|
||||||
|
});
|
||||||
//go back to jobs list.
|
//go back to jobs list.
|
||||||
history.push(`/manage/`);
|
history.push(`/manage/`);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export default async function DuplicateJob(
|
|||||||
delete existingJob.id;
|
delete existingJob.id;
|
||||||
delete existingJob.createdat;
|
delete existingJob.createdat;
|
||||||
delete existingJob.updatedat;
|
delete existingJob.updatedat;
|
||||||
|
delete existingJob.cieca_stl;
|
||||||
|
delete existingJob.cieca_ttl;
|
||||||
|
|
||||||
const newJob = {
|
const newJob = {
|
||||||
...existingJob,
|
...existingJob,
|
||||||
@@ -81,6 +83,8 @@ export async function CreateIouForJob(
|
|||||||
delete existingJob.id;
|
delete existingJob.id;
|
||||||
delete existingJob.createdat;
|
delete existingJob.createdat;
|
||||||
delete existingJob.updatedat;
|
delete existingJob.updatedat;
|
||||||
|
delete existingJob.cieca_stl;
|
||||||
|
delete existingJob.cieca_ttl;
|
||||||
|
|
||||||
const newJob = {
|
const newJob = {
|
||||||
...existingJob,
|
...existingJob,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
|
BranchesOutlined,
|
||||||
ExclamationCircleFilled,
|
ExclamationCircleFilled,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
WarningFilled,
|
WarningFilled,
|
||||||
BranchesOutlined,
|
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
|
import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
@@ -15,6 +15,7 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||||
import DataLabel from "../data-label/data-label.component";
|
import DataLabel from "../data-label/data-label.component";
|
||||||
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
||||||
@@ -122,20 +123,23 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
</DataLabel>
|
</DataLabel>
|
||||||
{job.cccontracts.length > 0 && (
|
{job.cccontracts.length > 0 && (
|
||||||
<DataLabel label={t("jobs.labels.contracts")}>
|
<DataLabel label={t("jobs.labels.contracts")}>
|
||||||
{job.cccontracts.map((c) => (
|
{job.cccontracts.map((c, index) => (
|
||||||
<Link
|
<Space wrap>
|
||||||
key={c.id}
|
<Link
|
||||||
to={`/manage/courtesycars/contracts/${c.id}`}
|
key={c.id}
|
||||||
>{`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`}</Link>
|
to={`/manage/courtesycars/contracts/${c.id}`}
|
||||||
|
>
|
||||||
|
{`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`}
|
||||||
|
{index !== job.cccontracts.length - 1 ? "," : null}
|
||||||
|
</Link>
|
||||||
|
</Space>
|
||||||
))}
|
))}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DataLabel label={t("jobs.fields.production_vars.note")}>
|
<DataLabel label={t("jobs.fields.production_vars.note")}>
|
||||||
<ProductionListColumnProductionNote record={job} />
|
<ProductionListColumnProductionNote record={job} />
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
|
<Space wrap>
|
||||||
<Space>
|
|
||||||
{job.special_coverage_policy && (
|
{job.special_coverage_policy && (
|
||||||
<Tag color="tomato">
|
<Tag color="tomato">
|
||||||
<Space>
|
<Space>
|
||||||
@@ -160,19 +164,35 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
<Card
|
<Card
|
||||||
style={{ height: "100%" }}
|
style={{ height: "100%" }}
|
||||||
title={
|
title={
|
||||||
<Link to={disabled ? "#" : `/manage/owners/${job.owner.id}`}>
|
disabled ? (
|
||||||
{ownerTitle.length > 0
|
<>
|
||||||
? ownerTitle
|
{ownerTitle.length > 0
|
||||||
: t("owner.labels.noownerinfo")}
|
? ownerTitle
|
||||||
</Link>
|
: t("owner.labels.noownerinfo")}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Link to={`/manage/owners/${job.owner.id}`}>
|
||||||
|
{ownerTitle.length > 0
|
||||||
|
? ownerTitle
|
||||||
|
: t("owner.labels.noownerinfo")}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
|
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
|
||||||
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
|
{disabled ? (
|
||||||
|
<PhoneNumberFormatter>{job.ownr_ph1}</PhoneNumberFormatter>
|
||||||
|
) : (
|
||||||
|
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
|
||||||
|
)}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
|
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
|
||||||
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
|
{disabled ? (
|
||||||
|
<PhoneNumberFormatter>{job.ownr_ph2}</PhoneNumberFormatter>
|
||||||
|
) : (
|
||||||
|
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
|
||||||
|
)}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel key="3" label={t("owners.fields.address")}>
|
<DataLabel key="3" label={t("owners.fields.address")}>
|
||||||
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
|
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
|
||||||
@@ -180,7 +200,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
} ${job.ownr_st || ""} ${job.ownr_zip || ""}`}
|
} ${job.ownr_st || ""} ${job.ownr_zip || ""}`}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel key="4" label={t("owners.fields.ownr_ea")}>
|
<DataLabel key="4" label={t("owners.fields.ownr_ea")}>
|
||||||
{job.ownr_ea || ""}
|
{disabled ? (
|
||||||
|
<>{job.ownr_ea || ""}</>
|
||||||
|
) : job.ownr_ea ? (
|
||||||
|
<a href={`mailto:${job.ownr_ea}`}>{job.ownr_ea}</a>
|
||||||
|
) : null}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
{job.owner?.tax_number && (
|
{job.owner?.tax_number && (
|
||||||
<DataLabel key="5" label={t("owners.fields.tax_number")}>
|
<DataLabel key="5" label={t("owners.fields.tax_number")}>
|
||||||
@@ -195,17 +219,19 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
style={{ height: "100%" }}
|
style={{ height: "100%" }}
|
||||||
title={
|
title={
|
||||||
job.vehicle ? (
|
job.vehicle ? (
|
||||||
<Link
|
disabled ? (
|
||||||
to={
|
<>
|
||||||
disabled
|
{vehicleTitle.length > 0
|
||||||
? "#"
|
? vehicleTitle
|
||||||
: job.vehicle && `/manage/vehicles/${job.vehicle.id}`
|
: t("vehicles.labels.novehinfo")}{" "}
|
||||||
}
|
</>
|
||||||
>
|
) : (
|
||||||
{vehicleTitle.length > 0
|
<Link to={job.vehicle && `/manage/vehicles/${job.vehicle.id}`}>
|
||||||
? vehicleTitle
|
{vehicleTitle.length > 0
|
||||||
: t("vehicles.labels.novehinfo")}
|
? vehicleTitle
|
||||||
</Link>
|
: t("vehicles.labels.novehinfo")}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<span></span>
|
<span></span>
|
||||||
)
|
)
|
||||||
@@ -221,9 +247,19 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
<VehicleVinDisplay>
|
<VehicleVinDisplay>
|
||||||
{`${job.v_vin || t("general.labels.na")}`}
|
{`${job.v_vin || t("general.labels.na")}`}
|
||||||
</VehicleVinDisplay>
|
</VehicleVinDisplay>
|
||||||
|
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
|
||||||
|
job.v_vin?.length !== 17 ? (
|
||||||
|
<WarningFilled
|
||||||
|
style={{ color: "tomato", marginLeft: ".3rem" }}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
) : null}
|
||||||
|
</DataLabel>
|
||||||
|
<DataLabel label={t("jobs.fields.regie_number")}>
|
||||||
|
{job.regie_number || t("general.labels.na")}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel label={t("jobs.labels.relatedros")}>
|
<DataLabel label={t("jobs.labels.relatedros")}>
|
||||||
<JobsRelatedRos jobid={job.id} job={job} />
|
<JobsRelatedRos jobid={job.id} job={job} disabled={disabled} />
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
{job.vehicle && job.vehicle.notes && (
|
{job.vehicle && job.vehicle.notes && (
|
||||||
<DataLabel
|
<DataLabel
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { Gallery } from "react-grid-gallery";
|
import { Gallery } from "react-grid-gallery";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { GenerateThumbUrl } from "./job-documents.utility";
|
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
|
||||||
|
|
||||||
function JobsDocumentGalleryExternal({
|
function JobsDocumentGalleryExternal({
|
||||||
data,
|
data,
|
||||||
@@ -15,7 +15,7 @@ function JobsDocumentGalleryExternal({
|
|||||||
let documents = data.reduce((acc, value) => {
|
let documents = data.reduce((acc, value) => {
|
||||||
if (value.type.startsWith("image")) {
|
if (value.type.startsWith("image")) {
|
||||||
acc.push({
|
acc.push({
|
||||||
//src: GenerateSrcUrl(value),
|
fullsize: GenerateSrcUrl(value),
|
||||||
src: GenerateThumbUrl(value),
|
src: GenerateThumbUrl(value),
|
||||||
thumbnailHeight: 225,
|
thumbnailHeight: 225,
|
||||||
thumbnailWidth: 225,
|
thumbnailWidth: 225,
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ function JobDocumentsLocalGalleryExternal({
|
|||||||
val.type.mime &&
|
val.type.mime &&
|
||||||
val.type.mime.startsWith("image")
|
val.type.mime.startsWith("image")
|
||||||
) {
|
) {
|
||||||
acc.push({ ...val, src: val.thumbnail });
|
acc.push({ ...val, src: val.thumbnail, fullsize: val.src });
|
||||||
}
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, [])
|
}, [])
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
import { UPDATE_JOBS } from "../../graphql/jobs.queries";
|
import { UPDATE_JOBS } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import client from "../../utils/GraphQLClient";
|
import client from "../../utils/GraphQLClient";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -20,6 +22,11 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
insertAuditTrail: ({ jobid, operation }) =>
|
||||||
|
dispatch(insertAuditTrail({ jobid, operation })),
|
||||||
|
});
|
||||||
|
|
||||||
function updateJobCache(items) {
|
function updateJobCache(items) {
|
||||||
client.cache.modify({
|
client.cache.modify({
|
||||||
id: "ROOT_QUERY",
|
id: "ROOT_QUERY",
|
||||||
@@ -41,6 +48,7 @@ export function JobsExportAllButton({
|
|||||||
loadingCallback,
|
loadingCallback,
|
||||||
completedCallback,
|
completedCallback,
|
||||||
refetch,
|
refetch,
|
||||||
|
insertAuditTrail,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updateJob] = useMutation(UPDATE_JOBS);
|
const [updateJob] = useMutation(UPDATE_JOBS);
|
||||||
@@ -177,6 +185,12 @@ export function JobsExportAllButton({
|
|||||||
key: "jobsuccessexport",
|
key: "jobsuccessexport",
|
||||||
message: t("jobs.successes.exported"),
|
message: t("jobs.successes.exported"),
|
||||||
});
|
});
|
||||||
|
jobUpdateResponse.data.update_jobs.returning.forEach((job) => {
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.jobexported(),
|
||||||
|
});
|
||||||
|
});
|
||||||
updateJobCache(
|
updateJobCache(
|
||||||
jobUpdateResponse.data.update_jobs.returning.map(
|
jobUpdateResponse.data.update_jobs.returning.map(
|
||||||
(job) => job.id
|
(job) => job.id
|
||||||
@@ -190,13 +204,17 @@ export function JobsExportAllButton({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
|
if (
|
||||||
|
bodyshop.accountingconfig &&
|
||||||
|
bodyshop.accountingconfig.qbo &&
|
||||||
|
successfulTransactions.length > 0
|
||||||
|
) {
|
||||||
notification.open({
|
notification.open({
|
||||||
type: "success",
|
type: "success",
|
||||||
key: "jobsuccessexport",
|
key: "jobsuccessexport",
|
||||||
message: t("jobs.successes.exported"),
|
message: t("jobs.successes.exported"),
|
||||||
});
|
});
|
||||||
updateJobCache([
|
const successfulTransactionsSet = [
|
||||||
...new Set(
|
...new Set(
|
||||||
successfulTransactions.map(
|
successfulTransactions.map(
|
||||||
(st) =>
|
(st) =>
|
||||||
@@ -207,7 +225,14 @@ export function JobsExportAllButton({
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
]);
|
];
|
||||||
|
if (successfulTransactionsSet.length > 0) {
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: successfulTransactionsSet[0],
|
||||||
|
operation: AuditTrailMapping.jobexported(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateJobCache(successfulTransactionsSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -225,4 +250,7 @@ export function JobsExportAllButton({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(JobsExportAllButton);
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(JobsExportAllButton);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Checkbox, Divider, Input, Table, Button } from "antd";
|
import { Button, Checkbox, Divider, Input, Space, Table } from "antd";
|
||||||
import React from "react";
|
import moment from "moment";
|
||||||
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||||
|
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
export default function JobsFindModalComponent({
|
export default function JobsFindModalComponent({
|
||||||
@@ -14,11 +16,15 @@ export default function JobsFindModalComponent({
|
|||||||
importOptionsState,
|
importOptionsState,
|
||||||
modalSearchState,
|
modalSearchState,
|
||||||
jobsListRefetch,
|
jobsListRefetch,
|
||||||
|
partsQueueToggle,
|
||||||
|
setPartsQueueToggle,
|
||||||
|
updateSchComp,
|
||||||
|
setSchComp,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [modalSearch, setModalSearch] = modalSearchState;
|
const [modalSearch, setModalSearch] = modalSearchState;
|
||||||
const [importOptions, setImportOptions] = importOptionsState;
|
const [importOptions, setImportOptions] = importOptionsState;
|
||||||
|
const [checkUTT, setCheckUTT] = useState(false);
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
@@ -140,6 +146,35 @@ export default function JobsFindModalComponent({
|
|||||||
if (record) {
|
if (record) {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
setSelectedJob(record.id);
|
setSelectedJob(record.id);
|
||||||
|
if (record.actual_in && record.scheduled_completion) {
|
||||||
|
setSchComp({
|
||||||
|
...updateSchComp,
|
||||||
|
actual_in: record.actual_in,
|
||||||
|
scheduled_completion: record.scheduled_completion,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (record.actual_in && !record.scheduled_completion) {
|
||||||
|
setSchComp({
|
||||||
|
...updateSchComp,
|
||||||
|
actual_in: record.actual_in,
|
||||||
|
scheduled_completion: moment(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!record.actual_in && record.scheduled_completion) {
|
||||||
|
setSchComp({
|
||||||
|
...updateSchComp,
|
||||||
|
actual_in: moment(),
|
||||||
|
scheduled_completion: moment(record.scheduled_completion),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!record.actual_in && !record.scheduled_completion) {
|
||||||
|
setSchComp({
|
||||||
|
...updateSchComp,
|
||||||
|
actual_in: moment(),
|
||||||
|
scheduled_completion: moment(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,6 +210,35 @@ export default function JobsFindModalComponent({
|
|||||||
rowSelection={{
|
rowSelection={{
|
||||||
onSelect: (props) => {
|
onSelect: (props) => {
|
||||||
setSelectedJob(props.id);
|
setSelectedJob(props.id);
|
||||||
|
if (props.actual_in && props.scheduled_completion) {
|
||||||
|
setSchComp({
|
||||||
|
...updateSchComp,
|
||||||
|
actual_in: props.actual_in,
|
||||||
|
scheduled_completion: props.scheduled_completion,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (props.actual_in && !props.scheduled_completion) {
|
||||||
|
setSchComp({
|
||||||
|
...updateSchComp,
|
||||||
|
actual_in: props.actual_in,
|
||||||
|
scheduled_completion: moment(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!props.actual_in && props.scheduled_completion) {
|
||||||
|
setSchComp({
|
||||||
|
...updateSchComp,
|
||||||
|
actual_in: moment(),
|
||||||
|
scheduled_completion: moment(props.scheduled_completion),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!props.actual_in && !props.scheduled_completion) {
|
||||||
|
setSchComp({
|
||||||
|
...updateSchComp,
|
||||||
|
actual_in: moment(),
|
||||||
|
scheduled_completion: moment(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
type: "radio",
|
type: "radio",
|
||||||
selectedRowKeys: [selectedJob],
|
selectedRowKeys: [selectedJob],
|
||||||
@@ -188,17 +252,58 @@ export default function JobsFindModalComponent({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Checkbox
|
<Space>
|
||||||
defaultChecked={importOptions.overrideHeader}
|
<Checkbox
|
||||||
onChange={(e) =>
|
defaultChecked={importOptions.overrideHeader}
|
||||||
setImportOptions({
|
onChange={(e) =>
|
||||||
...importOptions,
|
setImportOptions({
|
||||||
overrideHeaders: e.target.checked,
|
...importOptions,
|
||||||
})
|
overrideHeaders: e.target.checked,
|
||||||
}
|
})
|
||||||
>
|
}
|
||||||
{t("jobs.labels.override_header")}
|
>
|
||||||
</Checkbox>
|
{t("jobs.labels.override_header")}
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
checked={partsQueueToggle}
|
||||||
|
onChange={(e) => setPartsQueueToggle(e.target.checked)}
|
||||||
|
>
|
||||||
|
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
|
||||||
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
checked={updateSchComp.checked}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSchComp({ ...updateSchComp, checked: e.target.checked })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("jobs.labels.update_scheduled_completion")}
|
||||||
|
</Checkbox>
|
||||||
|
{updateSchComp.checked === true ? (
|
||||||
|
<>
|
||||||
|
{checkUTT === false ? (
|
||||||
|
<FormDateTimePickerComponent
|
||||||
|
value={updateSchComp.scheduled_completion}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSchComp({ ...updateSchComp, scheduled_completion: e });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<Checkbox
|
||||||
|
checked={checkUTT}
|
||||||
|
onChange={(e) => {
|
||||||
|
setCheckUTT(e.target.checked);
|
||||||
|
setSchComp({
|
||||||
|
...updateSchComp,
|
||||||
|
scheduled_completion: null,
|
||||||
|
automatic: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("jobs.labels.calc_scheuled_completion")}
|
||||||
|
</Checkbox>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ export default connect(
|
|||||||
setSelectedJob,
|
setSelectedJob,
|
||||||
importOptionsState,
|
importOptionsState,
|
||||||
modalSearchState,
|
modalSearchState,
|
||||||
|
partsQueueToggle,
|
||||||
|
setPartsQueueToggle,
|
||||||
|
updateSchComp,
|
||||||
|
setSchComp,
|
||||||
...modalProps
|
...modalProps
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -91,6 +95,10 @@ export default connect(
|
|||||||
jobsListRefetch={jobsList.refetch}
|
jobsListRefetch={jobsList.refetch}
|
||||||
jobsList={jobsData}
|
jobsList={jobsData}
|
||||||
modalSearchState={modalSearchState}
|
modalSearchState={modalSearchState}
|
||||||
|
partsQueueToggle={partsQueueToggle}
|
||||||
|
setPartsQueueToggle={setPartsQueueToggle}
|
||||||
|
updateSchComp={updateSchComp}
|
||||||
|
setSchComp={setSchComp}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { Link, useHistory, useLocation } from "react-router-dom";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
import { pageLimit } from "../../utils/config";
|
||||||
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -24,6 +26,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const [openSearchResults, setOpenSearchResults] = useState([]);
|
const [openSearchResults, setOpenSearchResults] = useState([]);
|
||||||
const [searchLoading, setSearchLoading] = useState(false);
|
const [searchLoading, setSearchLoading] = useState(false);
|
||||||
|
const [filter, setFilter] = useLocalStorage("filter_jobs_all", null);
|
||||||
const { page, sortcolumn, sortorder } = search;
|
const { page, sortcolumn, sortorder } = search;
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
@@ -33,17 +36,14 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
dataIndex: "ro_number",
|
dataIndex: "ro_number",
|
||||||
key: "ro_number",
|
key: "ro_number",
|
||||||
|
|
||||||
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
|
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||||
sortOrder: sortcolumn === "ro_number" && sortorder,
|
sortOrder: sortcolumn === "ro_number" && sortorder,
|
||||||
|
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link to={"/manage/jobs/" + record.id}>
|
<Link to={"/manage/jobs/" + record.id}>
|
||||||
{record.ro_number || t("general.labels.na")}
|
{record.ro_number || t("general.labels.na")}
|
||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.owner"),
|
title: t("jobs.fields.owner"),
|
||||||
dataIndex: "ownr_ln",
|
dataIndex: "ownr_ln",
|
||||||
@@ -95,6 +95,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.status || t("general.labels.na");
|
return record.status || t("general.labels.na");
|
||||||
},
|
},
|
||||||
|
filteredValue: filter?.status || null,
|
||||||
filters: bodyshop.md_ro_statuses.statuses.map((s) => {
|
filters: bodyshop.md_ro_statuses.statuses.map((s) => {
|
||||||
return { text: s, value: [s] };
|
return { text: s, value: [s] };
|
||||||
}),
|
}),
|
||||||
@@ -125,7 +126,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
title: t("vehicles.fields.plate_no"),
|
title: t("vehicles.fields.plate_no"),
|
||||||
dataIndex: "plate_no",
|
dataIndex: "plate_no",
|
||||||
key: "plate_no",
|
key: "plate_no",
|
||||||
|
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
|
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
|
||||||
sortOrder: sortcolumn === "plate_no" && sortorder,
|
sortOrder: sortcolumn === "plate_no" && sortorder,
|
||||||
@@ -137,7 +137,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
title: t("jobs.fields.clm_no"),
|
title: t("jobs.fields.clm_no"),
|
||||||
dataIndex: "clm_no",
|
dataIndex: "clm_no",
|
||||||
key: "clm_no",
|
key: "clm_no",
|
||||||
|
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
|
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||||
sortOrder: sortcolumn === "clm_no" && sortorder,
|
sortOrder: sortcolumn === "clm_no" && sortorder,
|
||||||
@@ -193,6 +192,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
} else {
|
} else {
|
||||||
delete search.statusFilters;
|
delete search.statusFilters;
|
||||||
}
|
}
|
||||||
|
setFilter(filters);
|
||||||
history.push({ search: queryString.stringify(search) });
|
history.push({ search: queryString.stringify(search) });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -259,11 +259,11 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
pagination={
|
pagination={
|
||||||
search?.search
|
search?.search
|
||||||
? {
|
? {
|
||||||
pageSize: 25,
|
pageSize: pageLimit,
|
||||||
showSizeChanger: false,
|
showSizeChanger: false,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
pageSize: 25,
|
pageSize: pageLimit,
|
||||||
current: parseInt(page || 1),
|
current: parseInt(page || 1),
|
||||||
total: total,
|
total: total,
|
||||||
showSizeChanger: false,
|
showSizeChanger: false,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
SyncOutlined,
|
BranchesOutlined,
|
||||||
ExclamationCircleFilled,
|
ExclamationCircleFilled,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
BranchesOutlined,
|
SyncOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
|
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
|
||||||
@@ -14,9 +14,10 @@ import { Link, useHistory, useLocation } from "react-router-dom";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { onlyUnique } from "../../utils/arrayHelper";
|
||||||
|
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||||
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
@@ -39,10 +40,8 @@ export function JobsList({ bodyshop }) {
|
|||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({ sortedInfo: {} });
|
||||||
sortedInfo: {},
|
const [filter, setFilter] = useLocalStorage("filter_jobs_list", null);
|
||||||
filteredInfo: { text: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -91,7 +90,8 @@ export function JobsList({ bodyshop }) {
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, sortedInfo: sorter });
|
||||||
|
setFilter(filters);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnRowClick = (record) => {
|
const handleOnRowClick = (record) => {
|
||||||
@@ -117,7 +117,6 @@ export function JobsList({ bodyshop }) {
|
|||||||
parseInt((b.ro_number || "0").replace(/\D/g, "")),
|
parseInt((b.ro_number || "0").replace(/\D/g, "")),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
|
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link
|
<Link
|
||||||
to={"/manage/jobs/" + record.id}
|
to={"/manage/jobs/" + record.id}
|
||||||
@@ -145,7 +144,6 @@ export function JobsList({ bodyshop }) {
|
|||||||
dataIndex: "owner",
|
dataIndex: "owner",
|
||||||
key: "owner",
|
key: "owner",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
@@ -185,16 +183,15 @@ export function JobsList({ bodyshop }) {
|
|||||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.status"),
|
title: t("jobs.fields.status"),
|
||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
key: "status",
|
key: "status",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
|
||||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
|
filteredValue: filter?.status || null,
|
||||||
filters:
|
filters:
|
||||||
(jobs &&
|
(jobs &&
|
||||||
jobs
|
jobs
|
||||||
@@ -205,7 +202,14 @@ export function JobsList({ bodyshop }) {
|
|||||||
text: s || "No Status*",
|
text: s || "No Status*",
|
||||||
value: [s],
|
value: [s],
|
||||||
};
|
};
|
||||||
})) ||
|
})
|
||||||
|
.sort((a, b) =>
|
||||||
|
statusSort(
|
||||||
|
a.text,
|
||||||
|
b.text,
|
||||||
|
bodyshop.md_ro_statuses.active_statuses
|
||||||
|
)
|
||||||
|
)) ||
|
||||||
[],
|
[],
|
||||||
onFilter: (value, record) => value.includes(record.status),
|
onFilter: (value, record) => value.includes(record.status),
|
||||||
},
|
},
|
||||||
@@ -262,6 +266,7 @@ export function JobsList({ bodyshop }) {
|
|||||||
dataIndex: "ins_co_nm",
|
dataIndex: "ins_co_nm",
|
||||||
key: "ins_co_nm",
|
key: "ins_co_nm",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
filteredValue: filter?.ins_co_nm || null,
|
||||||
filters:
|
filters:
|
||||||
(jobs &&
|
(jobs &&
|
||||||
jobs
|
jobs
|
||||||
@@ -269,10 +274,11 @@ export function JobsList({ bodyshop }) {
|
|||||||
.filter(onlyUnique)
|
.filter(onlyUnique)
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
return {
|
return {
|
||||||
text: s,
|
text: s || "No Ins. Co.*",
|
||||||
value: [s],
|
value: [s],
|
||||||
};
|
};
|
||||||
})) ||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
[],
|
[],
|
||||||
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
@@ -283,7 +289,6 @@ export function JobsList({ bodyshop }) {
|
|||||||
key: "clm_total",
|
key: "clm_total",
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
|
||||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
||||||
@@ -294,10 +299,11 @@ export function JobsList({ bodyshop }) {
|
|||||||
{
|
{
|
||||||
title: t("jobs.labels.estimator"),
|
title: t("jobs.labels.estimator"),
|
||||||
dataIndex: "jobs.labels.estimator",
|
dataIndex: "jobs.labels.estimator",
|
||||||
key: "jobs.labels.estimator",
|
key: "estimator",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["xl"],
|
responsive: ["xl"],
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
|
filteredValue: filter?.estimator || null,
|
||||||
filters:
|
filters:
|
||||||
(jobs &&
|
(jobs &&
|
||||||
jobs
|
jobs
|
||||||
@@ -305,10 +311,11 @@ export function JobsList({ bodyshop }) {
|
|||||||
.filter(onlyUnique)
|
.filter(onlyUnique)
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
return {
|
return {
|
||||||
text: s || "N/A",
|
text: s || "No Estimator*",
|
||||||
value: [s],
|
value: [s],
|
||||||
};
|
};
|
||||||
})) ||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
[],
|
[],
|
||||||
onFilter: (value, record) =>
|
onFilter: (value, record) =>
|
||||||
value.includes(
|
value.includes(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
|
BranchesOutlined,
|
||||||
ExclamationCircleFilled,
|
ExclamationCircleFilled,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
BranchesOutlined,
|
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
|
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
|
||||||
@@ -14,9 +14,11 @@ import { Link, useHistory, useLocation } from "react-router-dom";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { onlyUnique } from "../../utils/arrayHelper";
|
||||||
|
import { pageLimit } from "../../utils/config";
|
||||||
|
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||||
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
@@ -46,15 +48,14 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||||
variables: {
|
variables: {
|
||||||
statuses: readyStatuses,
|
statuses: readyStatuses,
|
||||||
|
isConverted: true,
|
||||||
},
|
},
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({ sortedInfo: {} });
|
||||||
sortedInfo: {},
|
const [filter, setFilter] = useLocalStorage("filter_jobs_ready", null);
|
||||||
filteredInfo: { text: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -103,7 +104,8 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, sortedInfo: sorter });
|
||||||
|
setFilter(filters);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnRowClick = (record) => {
|
const handleOnRowClick = (record) => {
|
||||||
@@ -127,7 +129,6 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
|
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link
|
<Link
|
||||||
to={"/manage/jobs/" + record.id}
|
to={"/manage/jobs/" + record.id}
|
||||||
@@ -155,7 +156,6 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
dataIndex: "owner",
|
dataIndex: "owner",
|
||||||
key: "owner",
|
key: "owner",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
@@ -195,16 +195,15 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.status"),
|
title: t("jobs.fields.status"),
|
||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
key: "status",
|
key: "status",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
|
||||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
|
filteredValue: filter?.status || null,
|
||||||
filters:
|
filters:
|
||||||
(jobs &&
|
(jobs &&
|
||||||
jobs
|
jobs
|
||||||
@@ -215,11 +214,17 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
text: s || "No Status*",
|
text: s || "No Status*",
|
||||||
value: [s],
|
value: [s],
|
||||||
};
|
};
|
||||||
})) ||
|
})
|
||||||
|
.sort((a, b) =>
|
||||||
|
statusSort(
|
||||||
|
a.text,
|
||||||
|
b.text,
|
||||||
|
bodyshop.md_ro_statuses.active_statuses
|
||||||
|
)
|
||||||
|
)) ||
|
||||||
[],
|
[],
|
||||||
onFilter: (value, record) => value.includes(record.status),
|
onFilter: (value, record) => value.includes(record.status),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: t("jobs.fields.vehicle"),
|
title: t("jobs.fields.vehicle"),
|
||||||
dataIndex: "vehicle",
|
dataIndex: "vehicle",
|
||||||
@@ -272,6 +277,7 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
dataIndex: "ins_co_nm",
|
dataIndex: "ins_co_nm",
|
||||||
key: "ins_co_nm",
|
key: "ins_co_nm",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
filteredValue: filter?.ins_co_nm || null,
|
||||||
filters:
|
filters:
|
||||||
(jobs &&
|
(jobs &&
|
||||||
jobs
|
jobs
|
||||||
@@ -279,10 +285,11 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
.filter(onlyUnique)
|
.filter(onlyUnique)
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
return {
|
return {
|
||||||
text: s,
|
text: s || "No Ins Co.*",
|
||||||
value: [s],
|
value: [s],
|
||||||
};
|
};
|
||||||
})) ||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
[],
|
[],
|
||||||
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
@@ -293,7 +300,6 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
key: "clm_total",
|
key: "clm_total",
|
||||||
responsive: ["md"],
|
responsive: ["md"],
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
|
|
||||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
||||||
@@ -304,9 +310,10 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
{
|
{
|
||||||
title: t("jobs.labels.estimator"),
|
title: t("jobs.labels.estimator"),
|
||||||
dataIndex: "jobs.labels.estimator",
|
dataIndex: "jobs.labels.estimator",
|
||||||
key: "jobs.labels.estimator",
|
key: "estimator",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["xl"],
|
responsive: ["xl"],
|
||||||
|
filteredValue: filter?.estimator || null,
|
||||||
filterSearch: true,
|
filterSearch: true,
|
||||||
filters:
|
filters:
|
||||||
(jobs &&
|
(jobs &&
|
||||||
@@ -315,10 +322,11 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
.filter(onlyUnique)
|
.filter(onlyUnique)
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
return {
|
return {
|
||||||
text: s || "N/A",
|
text: s || "No Estimator*",
|
||||||
value: [s],
|
value: [s],
|
||||||
};
|
};
|
||||||
})) ||
|
})
|
||||||
|
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||||
[],
|
[],
|
||||||
onFilter: (value, record) =>
|
onFilter: (value, record) =>
|
||||||
value.includes(
|
value.includes(
|
||||||
@@ -376,7 +384,7 @@ export function JobsReadyList({ bodyshop }) {
|
|||||||
>
|
>
|
||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={{ defaultPageSize: 50 }}
|
pagination={{ defaultPageSize: pageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={jobs}
|
dataSource={jobs}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Space, Tag } from "antd";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
export default function JobsRelatedRos({ jobid, job }) {
|
export default function JobsRelatedRos({ jobid, job, disabled }) {
|
||||||
if (!(job && job.vehicle && job.vehicle.jobs)) return null;
|
if (!(job && job.vehicle && job.vehicle.jobs)) return null;
|
||||||
return (
|
return (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
@@ -10,9 +10,15 @@ export default function JobsRelatedRos({ jobid, job }) {
|
|||||||
.filter((j) => j.id !== job.id)
|
.filter((j) => j.id !== job.id)
|
||||||
.map((j) => (
|
.map((j) => (
|
||||||
<Tag key={j.id}>
|
<Tag key={j.id}>
|
||||||
<Link to={`/manage/jobs/${j?.id}`}>{`${j.ro_number || "N/A"}${
|
{disabled ? (
|
||||||
j.clm_no ? ` | ${j.clm_no}` : ""
|
<>{`${j.ro_number || "N/A"}${j.clm_no ? ` | ${j.clm_no}` : ""}${
|
||||||
}${j.status ? ` | ${j.status}` : ""}`}</Link>
|
j.status ? ` | ${j.status}` : ""
|
||||||
|
}`}</>
|
||||||
|
) : (
|
||||||
|
<Link to={`/manage/jobs/${j?.id}`}>{`${j.ro_number || "N/A"}${
|
||||||
|
j.clm_no ? ` | ${j.clm_no}` : ""
|
||||||
|
}${j.status ? ` | ${j.status}` : ""}`}</Link>
|
||||||
|
)}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -231,7 +231,14 @@ export function LaborAllocationsTable({
|
|||||||
{summary.adjustments.toFixed(1)}
|
{summary.adjustments.toFixed(1)}
|
||||||
</Table.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<Table.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
{summary.difference.toFixed(1)}
|
<Typography.Text
|
||||||
|
style={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: summary.difference >= 0 ? "green" : "red",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{summary.difference.toFixed(1)}
|
||||||
|
</Typography.Text>
|
||||||
</Table.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
</Table.Summary.Row>
|
</Table.Summary.Row>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ export default function OwnerFindModalComponent({
|
|||||||
setSelectedOwner,
|
setSelectedOwner,
|
||||||
ownersListLoading,
|
ownersListLoading,
|
||||||
ownersList,
|
ownersList,
|
||||||
|
partsQueueToggle,
|
||||||
|
setPartsQueueToggle,
|
||||||
}) {
|
}) {
|
||||||
//setSelectedOwner is used to set the record id of the owner to use for adding the job.
|
//setSelectedOwner is used to set the record id of the owner to use for adding the job.
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t("owners.fields.ownr_ln"),
|
title: t("owners.fields.ownr_ln"),
|
||||||
@@ -109,6 +110,12 @@ export default function OwnerFindModalComponent({
|
|||||||
>
|
>
|
||||||
{t("owners.labels.create_new")}
|
{t("owners.labels.create_new")}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
<Checkbox
|
||||||
|
checked={partsQueueToggle}
|
||||||
|
onChange={(e) => setPartsQueueToggle(e.target.checked)}
|
||||||
|
>
|
||||||
|
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
|
||||||
|
</Checkbox>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user