Compare commits
276 Commits
feature/pa
...
rome/relea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93390bef63 | ||
|
|
9de3b5efb6 | ||
|
|
9ae53d090f | ||
|
|
e871ba600f | ||
|
|
fe3698980d | ||
|
|
677c7bb4cc | ||
|
|
d13a9cd04a | ||
|
|
c0dab92d0e | ||
|
|
4fdc241b9c | ||
|
|
28dc4867db | ||
|
|
1cf13da413 | ||
|
|
9c897972ad | ||
|
|
307e244475 | ||
|
|
9d3aca646b | ||
|
|
e6e61466df | ||
|
|
db7f9fe2ab | ||
|
|
ded798fdf1 | ||
|
|
bfe94e3068 | ||
|
|
823f07409a | ||
|
|
1a4bc720c2 | ||
|
|
18998c4dbe | ||
|
|
e236d6e912 | ||
|
|
523df670df | ||
|
|
ce58181fc3 | ||
|
|
bed0669f73 | ||
|
|
b0d077e104 | ||
|
|
bdb2951330 | ||
|
|
87a01208fb | ||
|
|
2daee84fbf | ||
|
|
cdbf58f3ac | ||
|
|
f6a59bdf55 | ||
|
|
e12edd977e | ||
|
|
b925c991eb | ||
|
|
1e7f43fe3d | ||
|
|
6f21de1901 | ||
|
|
25e8eaa1d4 | ||
|
|
6fe736ce06 | ||
|
|
482b03c2d1 | ||
|
|
ce3c72fc47 | ||
|
|
1ff5ed4141 | ||
|
|
10e3421572 | ||
|
|
0117237988 | ||
|
|
373fd817d0 | ||
|
|
0ef2d9646d | ||
|
|
c8ac417200 | ||
|
|
661bedbe5b | ||
|
|
2dd56590d3 | ||
|
|
98b760251c | ||
|
|
b97de32a44 | ||
|
|
3919efaa77 | ||
|
|
d915c123db | ||
|
|
92c8b54f85 | ||
|
|
d8420f472c | ||
|
|
34d93c4de0 | ||
|
|
1c400cd456 | ||
|
|
a3cf97fcab | ||
|
|
1a9dc7a377 | ||
|
|
806daebd3f | ||
|
|
dfd8845864 | ||
|
|
170108b339 | ||
|
|
9f1f58a9c7 | ||
|
|
4288e2d986 | ||
|
|
4b289388bf | ||
|
|
ed0136090c | ||
|
|
0d70545b98 | ||
|
|
2252091b53 | ||
|
|
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:
|
||||
api-deploy:
|
||||
docker:
|
||||
- image: "cimg/base:stable"
|
||||
- image: cimg/node:18.18.2
|
||||
steps:
|
||||
- checkout
|
||||
- eb/setup
|
||||
- run:
|
||||
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 deploy
|
||||
eb status
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
- eb/setup
|
||||
- run:
|
||||
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 deploy
|
||||
eb status
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -117,4 +117,6 @@ logs/oAuthClient-log.log
|
||||
|
||||
.node-persist/**
|
||||
|
||||
/*.env.*
|
||||
/*.env.*
|
||||
.idea/*
|
||||
.idea
|
||||
@@ -364,6 +364,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>unblock</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>viewjob</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -18432,6 +18453,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>tvmode</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>unknown</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -20059,6 +20101,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>create_ppc</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>db_price</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -24841,6 +24904,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>dms_model_override</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>dms_unsold</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -32462,6 +32546,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>total_sales_tax</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>totals</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -44869,6 +44974,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>hours_sold_detail_closed_estimator</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>hours_sold_detail_closed_ins_co</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -44953,6 +45079,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>hours_sold_detail_open_estimator</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>hours_sold_detail_open_ins_co</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -45037,6 +45184,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>hours_sold_summary_closed_estimator</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>hours_sold_summary_closed_ins_co</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -45121,6 +45289,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>hours_sold_summary_open_estimator</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>hours_sold_summary_open_ins_co</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -45457,6 +45646,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>open_orders_referral</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>open_orders_specific_csr</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -46773,6 +46983,27 @@
|
||||
<folder_node>
|
||||
<name>labels</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>allemployeetimetickets</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>asoftodaytarget</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -46794,6 +47025,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>body</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>bodycharttitle</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>calendarperiod</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -46815,6 +47088,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>combinedcharttitle</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>dailyactual</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -46983,6 +47277,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>priorweek</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>productivestatistics</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -47025,6 +47340,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>refinish</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>refinishcharttitle</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>targets</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -47109,6 +47466,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>timeticketsemployee</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>todateactual</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
24522
client/package-lock.json
generated
Normal file
24522
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
"@apollo/client": "^3.7.9",
|
||||
"@asseinfo/react-kanban": "^2.2.0",
|
||||
"@craco/craco": "^7.0.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
||||
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@sentry/react": "^7.40.0",
|
||||
"@sentry/tracing": "^7.40.0",
|
||||
@@ -89,8 +89,8 @@
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"start": "craco start",
|
||||
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
||||
"build:test": "env-cmd -f .env.test yarn run build",
|
||||
"build-deploy:test": "yarn run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
|
||||
"build:test": "env-cmd -f .env.test npm run build",
|
||||
"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",
|
||||
"test": "cypress open",
|
||||
"eject": "react-scripts eject",
|
||||
|
||||
@@ -7,6 +7,75 @@
|
||||
<meta name="theme-color" content="#002366" />
|
||||
<meta name="description" content="Rome Online" />
|
||||
<!-- <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" />
|
||||
<script>
|
||||
!(function () {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
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 {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -210,7 +211,7 @@ export function AccountingPayablesTableComponent({
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top", pageSize: 50 }}
|
||||
pagination={{ position: "top", pageSize: pageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
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 PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -209,7 +210,7 @@ export function AccountingPayablesTableComponent({
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top", pageSize: 50 }}
|
||||
pagination={{ position: "top", pageSize: pageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { alphaSort } from "../../utils/sorters";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function AuditTrailListComponent({ loading, data }) {
|
||||
const [state, setState] = useState({
|
||||
@@ -74,7 +75,7 @@ export default function AuditTrailListComponent({ loading, data }) {
|
||||
<Table
|
||||
{...formItemLayout}
|
||||
loading={loading}
|
||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function EmailAuditTrailListComponent({ loading, data }) {
|
||||
const [state, setState] = useState({
|
||||
@@ -53,7 +54,7 @@ export default function EmailAuditTrailListComponent({ loading, data }) {
|
||||
<Table
|
||||
{...formItemLayout}
|
||||
loading={loading}
|
||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
DELETE_BILL_LINE,
|
||||
INSERT_NEW_BILL_LINES,
|
||||
UPDATE_BILL_LINE
|
||||
UPDATE_BILL_LINE,
|
||||
} from "../../graphql/bill-lines.queries";
|
||||
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
@@ -20,6 +20,7 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||
import BillPrintButton from "../bill-print-button/bill-print-button.component";
|
||||
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
|
||||
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
@@ -176,7 +177,7 @@ export function BillDetailEditcontainer({
|
||||
extra={
|
||||
<Space>
|
||||
<BillDetailEditReturn data={data} />
|
||||
|
||||
<BillPrintButton billid={search.billid} />
|
||||
<Popconfirm
|
||||
visible={visible}
|
||||
onConfirm={() => form.submit()}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
|
||||
import _ from "lodash";
|
||||
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 { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
billEnterModal: selectBillEnterModal,
|
||||
@@ -38,8 +38,8 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
insertAuditTrail: ({ jobid, billid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, billid, operation })),
|
||||
});
|
||||
|
||||
const Templates = TemplateList("job_special");
|
||||
@@ -72,6 +72,11 @@ function BillEnterModalContainer({
|
||||
const formValues = useMemo(() => {
|
||||
return {
|
||||
...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:
|
||||
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
|
||||
federal_tax_rate:
|
||||
@@ -120,6 +125,8 @@ function BillEnterModalContainer({
|
||||
lbr_adjustment,
|
||||
location: lineLocation,
|
||||
part_type,
|
||||
create_ppc,
|
||||
original_actual_price,
|
||||
...restI
|
||||
} = i;
|
||||
|
||||
@@ -167,7 +174,7 @@ function BillEnterModalContainer({
|
||||
},
|
||||
],
|
||||
},
|
||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
|
||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
@@ -292,6 +299,14 @@ function BillEnterModalContainer({
|
||||
location: li.location || location,
|
||||
status:
|
||||
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 +369,9 @@ function BillEnterModalContainer({
|
||||
insertAuditTrail({
|
||||
jobid: values.jobid,
|
||||
billid: billId,
|
||||
operation: AuditTrailMapping.billposted(remainingValues.invoice_number),
|
||||
operation: AuditTrailMapping.billposted(
|
||||
r1.data.insert_bills.returning[0].invoice_number
|
||||
),
|
||||
});
|
||||
|
||||
if (enterAgain) {
|
||||
|
||||
@@ -50,6 +50,7 @@ export function BillFormComponent({
|
||||
job,
|
||||
loadOutstandingReturns,
|
||||
loadInventory,
|
||||
preferredMake,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
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(() => {
|
||||
if (job) form.validateFields(["is_credit_memo"]);
|
||||
}, [job, form]);
|
||||
@@ -185,6 +200,7 @@ export function BillFormComponent({
|
||||
<VendorSearchSelect
|
||||
disabled={disabled}
|
||||
options={vendorAutoCompleteOptions}
|
||||
preferredMake={preferredMake}
|
||||
onSelect={handleVendorSelect}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -389,7 +405,19 @@ export function BillFormComponent({
|
||||
// <CurrencyInput min={0} />
|
||||
// </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([
|
||||
"billlines",
|
||||
@@ -407,7 +435,7 @@ export function BillFormComponent({
|
||||
totals = CalculateBillTotal(values);
|
||||
if (!!totals)
|
||||
return (
|
||||
<div>
|
||||
<div align="right">
|
||||
<Space wrap>
|
||||
<Statistic
|
||||
title={t("bills.labels.subtotal")}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
||||
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 { 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 { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
|
||||
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
|
||||
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import BillFormComponent from "./bill-form.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -59,6 +59,7 @@ export function BillFormContainer({
|
||||
disableInvNumber={disableInvNumber}
|
||||
loadOutstandingReturns={loadOutstandingReturns}
|
||||
loadInventory={loadInventory}
|
||||
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
||||
/>
|
||||
{!billEdit && (
|
||||
<BillCmdReturnsTableComponent
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
@@ -101,6 +102,7 @@ export function BillEnterModalLinesComponent({
|
||||
line_desc: opt.line_desc,
|
||||
quantity: opt.part_qty || 1,
|
||||
actual_price: opt.cost,
|
||||
original_actual_price: opt.cost,
|
||||
cost_center: opt.part_type
|
||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
||||
? 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"),
|
||||
@@ -368,7 +407,7 @@ export function BillEnterModalLinesComponent({
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
additional: (record, index) => (
|
||||
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
|
||||
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
||||
{() => {
|
||||
const price = getFieldValue([
|
||||
"billlines",
|
||||
@@ -673,7 +712,7 @@ const EditableCell = ({
|
||||
if (additional)
|
||||
return (
|
||||
<td {...restProps}>
|
||||
<Space size="small">
|
||||
<div size="small">
|
||||
<Form.Item
|
||||
name={dataIndex}
|
||||
labelCol={{ span: 0 }}
|
||||
@@ -682,7 +721,7 @@ const EditableCell = ({
|
||||
{(formInput && formInput(record, record.name)) || children}
|
||||
</Form.Item>
|
||||
{additional && additional(record, record.name)}
|
||||
</Space>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
|
||||
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
|
||||
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";
|
||||
|
||||
export default function ChatConversationTitle({ conversation }) {
|
||||
@@ -13,6 +14,7 @@ export default function ChatConversationTitle({ conversation }) {
|
||||
{conversation && conversation.phone_num}
|
||||
</PhoneNumberFormatter>
|
||||
<ChatLabelComponent conversation={conversation} />
|
||||
<ChatPrintButton conversation={conversation} />
|
||||
<ChatConversationTitleTags
|
||||
jobConversations={
|
||||
(conversation && conversation.job_conversations) || []
|
||||
|
||||
@@ -8,15 +8,23 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
searchingForConversation: searchingForConversation,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
});
|
||||
|
||||
export function ChatOpenButton({ bodyshop, phone, jobid, openChatByPhone }) {
|
||||
export function ChatOpenButton({
|
||||
bodyshop,
|
||||
searchingForConversation,
|
||||
phone,
|
||||
jobid,
|
||||
openChatByPhone,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
if (!phone) return <></>;
|
||||
|
||||
@@ -29,7 +37,7 @@ export function ChatOpenButton({ bodyshop, phone, jobid, openChatByPhone }) {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const p = parsePhoneNumber(phone, "CA");
|
||||
|
||||
if (searchingForConversation) return; //This is to prevent finding the same thing twice.
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid });
|
||||
} else {
|
||||
|
||||
@@ -132,7 +132,7 @@ export function ChatPopupComponent({
|
||||
onClick={() => toggleChatVisible()}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<MessageOutlined />
|
||||
<MessageOutlined className="chat-popup-info-icon" />
|
||||
<strong>{t("messaging.labels.messaging")}</strong>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.chat-popup-info-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 992px) {
|
||||
.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,
|
||||
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"),
|
||||
dataIndex: "year",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function ContractsJobsComponent({
|
||||
loading,
|
||||
@@ -175,7 +176,7 @@ export default function ContractsJobsComponent({
|
||||
loading={loading}
|
||||
pagination={{
|
||||
position: "top",
|
||||
defaultPageSize: 10,
|
||||
defaultPageSize: pageLimit,
|
||||
defaultCurrent: defaultCurrent,
|
||||
}}
|
||||
columns={columns}
|
||||
|
||||
@@ -2,10 +2,10 @@ import { useQuery } from "@apollo/client";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import ContractJobsComponent from "./contract-jobs.component";
|
||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -15,6 +15,7 @@ export function ContractJobsContainer({ selectedJobState, bodyshop }) {
|
||||
const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||
variables: {
|
||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"],
|
||||
isConverted: true,
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
|
||||
@@ -4,15 +4,16 @@ import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
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 { createStructuredSelector } from "reselect";
|
||||
import moment from "moment";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -209,7 +210,7 @@ export function ContractsList({
|
||||
}}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: 25,
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
}}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function CourtesyCarContractListComponent({
|
||||
contracts,
|
||||
@@ -89,7 +90,7 @@ export default function CourtesyCarContractListComponent({
|
||||
scroll={{ x: true }}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: 25,
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
total: totalContracts,
|
||||
}}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { CHECK_CC_FLEET_NUMBER } from "../../graphql/courtesy-car.queries";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
|
||||
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
//import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
@@ -34,6 +35,18 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
|
||||
{/* <FormFieldsChanged form={form} /> */}
|
||||
<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
|
||||
label={t("courtesycars.fields.make")}
|
||||
name="make"
|
||||
@@ -58,18 +71,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.year")}
|
||||
name="year"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.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
|
||||
label={t("courtesycars.fields.fleetnumber")}
|
||||
@@ -213,49 +214,27 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
>
|
||||
<CourtesyCarStatus />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.nextservicekm")}
|
||||
name="nextservicekm"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
<Form.Item label={t("courtesycars.fields.readiness")} name="readiness">
|
||||
<CourtesyCarReadiness />
|
||||
</Form.Item>
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.nextservicedate")}
|
||||
name="nextservicedate"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
label={t("courtesycars.fields.nextservicekm")}
|
||||
name="nextservicekm"
|
||||
>
|
||||
<FormDatePicker />
|
||||
<InputNumber min={0} precision={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
shouldUpdate={(p, c) =>
|
||||
p.mileage !== c.mileage ||
|
||||
p.nextservicedate !== c.nextservicedate ||
|
||||
p.nextservicekm !== c.nextservicekm
|
||||
p.mileage !== c.mileage || p.nextservicekm !== c.nextservicekm
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
const nextservicedate = form.getFieldValue("nextservicedate");
|
||||
const nextservicekm = form.getFieldValue("nextservicekm");
|
||||
|
||||
const mileageOver =
|
||||
nextservicekm <= form.getFieldValue("mileage");
|
||||
|
||||
const dueForService =
|
||||
nextservicedate && moment(nextservicedate).isBefore(moment());
|
||||
|
||||
if (mileageOver || dueForService)
|
||||
const mileageOver = nextservicekm
|
||||
? nextservicekm <= form.getFieldValue("mileage")
|
||||
: false;
|
||||
if (mileageOver)
|
||||
return (
|
||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||
<span>
|
||||
@@ -263,6 +242,36 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
{t("contracts.labels.cardueforservice")}
|
||||
</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>
|
||||
<DateFormatter>{nextservicedate}</DateFormatter>
|
||||
</span>
|
||||
@@ -283,12 +292,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.registrationexpires")}
|
||||
name="registrationexpires"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
@@ -300,7 +303,8 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
{() => {
|
||||
const expires = form.getFieldValue("registrationexpires");
|
||||
|
||||
const dateover = expires && moment(expires).isBefore(moment());
|
||||
const dateover =
|
||||
expires && moment(expires).endOf("day").isBefore(moment());
|
||||
|
||||
if (dateover)
|
||||
return (
|
||||
@@ -330,14 +334,13 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
shouldUpdate={(p, c) =>
|
||||
p.insuranceexpires !== c.insuranceexpires
|
||||
}
|
||||
shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}
|
||||
>
|
||||
{() => {
|
||||
const expires = form.getFieldValue("insuranceexpires");
|
||||
|
||||
const dateover = expires && moment(expires).isBefore(moment());
|
||||
const dateover =
|
||||
expires && moment(expires).endOf("day").isBefore(moment());
|
||||
|
||||
if (dateover)
|
||||
return (
|
||||
|
||||
@@ -34,6 +34,32 @@ const CourtesyCarFuelComponent = (props, ref) => {
|
||||
step={null}
|
||||
style={{ marginLeft: "2rem", marginRight: "2rem" }}
|
||||
{...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,15 +9,15 @@ import {
|
||||
Table,
|
||||
Tooltip,
|
||||
} from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
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 { TemplateList } from "../../utils/TemplateConstants";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
@@ -74,10 +74,11 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
render: (text, record) => {
|
||||
const { nextservicedate, nextservicekm, mileage } = record;
|
||||
|
||||
const mileageOver = nextservicekm <= mileage;
|
||||
const mileageOver = nextservicekm ? nextservicekm <= mileage : false;
|
||||
|
||||
const dueForService =
|
||||
nextservicedate && moment(nextservicedate).isBefore(moment());
|
||||
nextservicedate &&
|
||||
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
|
||||
|
||||
return (
|
||||
<Space>
|
||||
@@ -91,6 +92,26 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("courtesycars.fields.readiness"),
|
||||
dataIndex: "readiness",
|
||||
key: "readiness",
|
||||
sorter: (a, b) => alphaSort(a.readiness, b.readiness),
|
||||
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"),
|
||||
dataIndex: "year",
|
||||
@@ -115,6 +136,14 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
sortOrder:
|
||||
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"),
|
||||
dataIndex: "plate",
|
||||
@@ -123,6 +152,36 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
sortOrder:
|
||||
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"),
|
||||
dataIndex: "outwith",
|
||||
@@ -166,6 +225,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
(c.year || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(c.make || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(c.model || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(c.plate || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
: courtesycars;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function CsiResponseListPaginated({
|
||||
refetch,
|
||||
@@ -106,7 +107,7 @@ export default function CsiResponseListPaginated({
|
||||
loading={loading}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: 25,
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
}}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { alphaSort } from "../../../utils/sorters";
|
||||
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||
import Dinero from "dinero.js";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import {pageLimit} from "../../../utils/config";
|
||||
|
||||
export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -118,7 +119,7 @@ export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 4em)" }}
|
||||
rowKey="id"
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Link } from "react-router-dom";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import {pageLimit} from "../../../utils/config";
|
||||
|
||||
export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -195,7 +196,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Link } from "react-router-dom";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import {pageLimit} from "../../../utils/config";
|
||||
|
||||
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -165,7 +166,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -117,7 +118,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
||||
}
|
||||
>
|
||||
<Table
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
rowKey={(record) => `${record.InvoiceNumber}${record.Account}`}
|
||||
dataSource={allocationsSummary}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import Dinero from "dinero.js";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -94,7 +95,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
|
||||
<Alert type="warning" message={t("jobs.labels.dms.disablebillwip")} />
|
||||
)}
|
||||
<Table
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
rowKey="center"
|
||||
dataSource={allocationsSummary}
|
||||
|
||||
@@ -191,6 +191,13 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_model_override"
|
||||
label={t("jobs.fields.dms.dms_model_override")}
|
||||
initialValue={false}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</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,
|
||||
});
|
||||
notification["success"]({ message: t("emails.successes.sent") });
|
||||
|
||||
@@ -65,8 +65,17 @@ export function FormDatePicker({
|
||||
});
|
||||
}
|
||||
|
||||
if (_a.isValid() && onChange)
|
||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||
if (_a.isValid() && onChange) {
|
||||
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 (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { forwardRef } from "react";
|
||||
//import DatePicker from "react-datepicker";
|
||||
//import "react-datepicker/src/stylesheets/datepicker.scss";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import { TimePicker } from "antd";
|
||||
import moment from "moment";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
//To be used as a form element only.
|
||||
|
||||
const DateTimePicker = (
|
||||
@@ -26,20 +26,21 @@ const DateTimePicker = (
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
onlyFuture={onlyFuture}
|
||||
isDateOnly={false}
|
||||
/>
|
||||
|
||||
<TimePicker
|
||||
value={value ? moment(value) : null}
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => moment().isAfter(d),
|
||||
})}
|
||||
onChange={onChange}
|
||||
showSecond={false}
|
||||
minuteStep={15}
|
||||
onBlur={onBlur}
|
||||
format="hh:mm a"
|
||||
{...restProps}
|
||||
value={value ? moment(value) : null}
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => moment().isAfter(d),
|
||||
})}
|
||||
onChange={onChange}
|
||||
showSecond={false}
|
||||
minuteStep={15}
|
||||
onBlur={onBlur}
|
||||
format="hh:mm a"
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -54,6 +54,7 @@ export default function GlobalSearchOs() {
|
||||
job.v_make_desc || ""
|
||||
} ${job.v_model_desc || ""}`}</span>
|
||||
<span>{`${job.clm_no || ""}`}</span>
|
||||
<span>{`${job.plate_no || ""}`}</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
|
||||
@@ -11,6 +11,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component";
|
||||
import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -213,7 +214,7 @@ export function JobsList({
|
||||
loading={loading}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: 25,
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
}}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "../../redux/application/application.actions";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import InventoryListPaginated from "./inventory-list.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//bodyshop: selectBodyshop,
|
||||
@@ -32,8 +33,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) {
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
search: search || "",
|
||||
offset: page ? (page - 1) * 25 : 0,
|
||||
limit: 25,
|
||||
offset: page ? (page - 1) * pageLimit : 0,
|
||||
limit: pageLimit,
|
||||
consumedIsNull: showall === "true" ? null : true,
|
||||
order: [
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Menu,
|
||||
notification,
|
||||
Popover,
|
||||
@@ -34,6 +35,8 @@ import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.
|
||||
import ScheduleAtChange from "./job-at-change.component";
|
||||
import ScheduleEventColor from "./schedule-event.color.component";
|
||||
import ScheduleEventNote from "./schedule-event.note.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -58,16 +61,44 @@ export function ScheduleEventComponent({
|
||||
const [visible, setVisible] = useState(false);
|
||||
const history = useHistory();
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
const [title, setTitle] = useState(event.title);
|
||||
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
|
||||
onClick={() => handleCancel({ id: event.id })}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("appointments.actions.cancel")}
|
||||
{t("appointments.actions.unblock")}
|
||||
</Button>
|
||||
</div>
|
||||
</Space>
|
||||
);
|
||||
|
||||
const popoverContent = (
|
||||
|
||||
@@ -2,12 +2,16 @@ import { useMutation } from "@apollo/client";
|
||||
import { notification } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.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";
|
||||
|
||||
export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
@@ -34,16 +38,24 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
||||
const jobUpdate = await updateJob({
|
||||
variables: {
|
||||
jobId: event.job.id,
|
||||
|
||||
job: {
|
||||
date_scheduled: null,
|
||||
scheduled_in: null,
|
||||
scheduled_completion: null,
|
||||
lost_sale_reason,
|
||||
date_lost_sale: new Date(),
|
||||
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) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.updating", {
|
||||
|
||||
@@ -87,7 +87,8 @@ export default function JobBillsTotalComponent({
|
||||
const totalPartsSublet = Dinero(totals.parts.parts.total)
|
||||
.add(Dinero(totals.parts.sublets.total))
|
||||
.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);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Form, Input, notification, Switch } from "antd";
|
||||
import moment from "moment-business-days";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -12,16 +13,15 @@ import {
|
||||
MARK_LATEST_APPOINTMENT_ARRIVED,
|
||||
} from "../../../../graphql/appointments.queries";
|
||||
import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
|
||||
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
|
||||
import { insertAuditTrail } from "../../../../redux/application/application.actions";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
|
||||
import ConfigFormComponents from "../../../config-form-components/config-form-components.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({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -230,6 +230,7 @@ export function JobChecklistForm({
|
||||
)),
|
||||
scheduled_delivery:
|
||||
job.scheduled_delivery && moment(job.scheduled_delivery),
|
||||
production_vars: job.production_vars,
|
||||
}),
|
||||
...(type === "deliver" && {
|
||||
removeFromProduction: true,
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import Dinero from "dinero.js";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
export default function JobCostingPartsTable({ data, summaryData }) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [state, setState] = useState({
|
||||
@@ -98,7 +99,7 @@ export default function JobCostingPartsTable({ data, summaryData }) {
|
||||
x: "50%", //y: "40rem"
|
||||
}}
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={filteredData}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { notification, Select, Space } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -56,8 +56,10 @@ export function JobLineLocationPopup({ bodyshop, jobline, disabled }) {
|
||||
<LoadingSpinner loading={loading}>
|
||||
<Select
|
||||
autoFocus
|
||||
allowClear
|
||||
dropdownMatchSelectWidth={100}
|
||||
value={location}
|
||||
onClear={() => setLocation(null)}
|
||||
onSelect={handleChange}
|
||||
onBlur={handleSave}
|
||||
>
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function JobLinesBillRefernece({ jobline }) {
|
||||
return (
|
||||
<div style={{ color: subletRequired && "tomato" }}>
|
||||
{subletRequired && <WarningFilled />}
|
||||
{`${(billLine.actual_price * billLine.quantity).toFixed(2)} (${
|
||||
{`${billLine.actual_price.toFixed(2)} x ${billLine.quantity} (${
|
||||
billLine.bill.vendor.name
|
||||
} #${billLine.bill.invoice_number})`}
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,8 @@ export default function JobReconciliationModalComponent({ job, bills }) {
|
||||
|
||||
const jobLineData = job.joblines.filter(
|
||||
(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.toLowerCase().includes("towing") &&
|
||||
j.lbr_op === "OP13") ||
|
||||
|
||||
@@ -33,7 +33,9 @@ const JobSearchSelect = (
|
||||
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@@ -32,42 +32,105 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||
...(job.job_totals.totals.us_sales_tax_breakdown
|
||||
? [
|
||||
{
|
||||
key:
|
||||
key: `${
|
||||
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,
|
||||
},
|
||||
{
|
||||
key:
|
||||
key: `${
|
||||
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,
|
||||
},
|
||||
{
|
||||
key:
|
||||
key: `${
|
||||
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,
|
||||
},
|
||||
{
|
||||
key:
|
||||
key: `${
|
||||
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,
|
||||
},
|
||||
{
|
||||
key:
|
||||
key: `${
|
||||
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,
|
||||
},
|
||||
]
|
||||
{
|
||||
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"),
|
||||
total: job.job_totals.totals.state_tax,
|
||||
},
|
||||
]),
|
||||
|
||||
{
|
||||
key: t("jobs.labels.total_repairs"),
|
||||
total: job.job_totals.totals.total_repairs,
|
||||
@@ -101,7 +164,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||
bold: true,
|
||||
},
|
||||
];
|
||||
}, [job.job_totals, t, bodyshop.md_responsibility_centers]);
|
||||
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { DateTimeFormat } from "./../../utils/DateFormatter";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -53,7 +54,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
||||
operation: AuditTrailMapping.admin_jobfieldchange(
|
||||
key,
|
||||
changedAuditFields[key] instanceof moment
|
||||
? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
|
||||
? DateTimeFormat(changedAuditFields[key])
|
||||
: changedAuditFields[key]
|
||||
),
|
||||
});
|
||||
@@ -179,6 +180,12 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
||||
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.date_lost_sale")}
|
||||
name="date_lost_sale"
|
||||
>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</Form>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { gql } from "@apollo/client";
|
||||
import _ from "lodash";
|
||||
import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
|
||||
export const GetSupplementDelta = async (client, jobId, newLines) => {
|
||||
const {
|
||||
@@ -50,6 +50,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
|
||||
.reduce((acc, value, idx) => {
|
||||
return acc + generateRemoveQuery(value, idx);
|
||||
}, "");
|
||||
//console.log(insertQueries, updateQueries, removeQueries);
|
||||
|
||||
if ((insertQueries + updateQueries + removeQueries).trim() === "") {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -114,7 +114,7 @@ const headerFields = [
|
||||
"ins_ct_ph",
|
||||
"ins_ct_phx",
|
||||
"loss_cat",
|
||||
//ad2
|
||||
//AD2
|
||||
"clmt_ln",
|
||||
"clmt_fn",
|
||||
"clmt_title",
|
||||
@@ -219,7 +219,16 @@ const headerFields = [
|
||||
"loc_title",
|
||||
"loc_ph",
|
||||
"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;
|
||||
|
||||
@@ -72,6 +72,8 @@ export function JobsAvailableContainer({
|
||||
|
||||
const [selectedJob, setSelectedJob] = useState(null);
|
||||
const [selectedOwner, setSelectedOwner] = useState(null);
|
||||
const [partsQueueToggle, setPartsQueueToggle] = useState(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||
|
||||
const [insertLoading, setInsertLoading] = useState(false);
|
||||
|
||||
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
||||
@@ -93,6 +95,7 @@ export function JobsAvailableContainer({
|
||||
logImEXEvent("job_import_new");
|
||||
|
||||
setOwnerModalVisible(false);
|
||||
|
||||
setInsertLoading(true);
|
||||
const estData = replaceEmpty(
|
||||
lazyData?.available_jobs_by_pk || estDataRaw.data.available_jobs_by_pk
|
||||
@@ -124,7 +127,7 @@ export function JobsAvailableContainer({
|
||||
|
||||
let existingVehicles;
|
||||
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({
|
||||
query: SEARCH_VEHICLE_BY_VIN,
|
||||
variables: {
|
||||
@@ -147,7 +150,7 @@ export function JobsAvailableContainer({
|
||||
text: t("jobs.labels.importnote"),
|
||||
},
|
||||
},
|
||||
queued_for_parts: true,
|
||||
queued_for_parts: partsQueueToggle,
|
||||
...(existingVehicles && existingVehicles.data.vehicles.length > 0
|
||||
? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null }
|
||||
: {}),
|
||||
@@ -197,6 +200,8 @@ export function JobsAvailableContainer({
|
||||
refetch();
|
||||
setInsertLoading(false);
|
||||
});
|
||||
|
||||
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||
} catch (r) {
|
||||
//error while inserting
|
||||
notification["error"]({
|
||||
@@ -204,10 +209,12 @@ export function JobsAvailableContainer({
|
||||
});
|
||||
refetch();
|
||||
setInsertLoading(false);
|
||||
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
//Suplement scenario
|
||||
//Supplement scenario
|
||||
const onJobFindModalOk = async () => {
|
||||
logImEXEvent("job_import_supplement");
|
||||
|
||||
@@ -260,11 +267,14 @@ export function JobsAvailableContainer({
|
||||
// "0.00"
|
||||
// ),
|
||||
// job_totals: newTotals,
|
||||
// queued_for_parts: true,
|
||||
queued_for_parts: partsQueueToggle,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (CriticalPartsScanning.treatment === "on") {
|
||||
|
||||
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||
|
||||
if (CriticalPartsScanning.treatment === "on") {
|
||||
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
|
||||
}
|
||||
if (updateResult.errors) {
|
||||
@@ -339,12 +349,14 @@ export function JobsAvailableContainer({
|
||||
const onOwnerModalCancel = () => {
|
||||
setOwnerModalVisible(false);
|
||||
setSelectedOwner(null);
|
||||
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||
};
|
||||
|
||||
const onJobModalCancel = () => {
|
||||
setJobModalVisible(false);
|
||||
modalSearchState[1]("");
|
||||
setSelectedJob(null);
|
||||
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||
};
|
||||
|
||||
const addJobAsNew = (record) => {
|
||||
@@ -365,6 +377,8 @@ export function JobsAvailableContainer({
|
||||
}, [addJobAsSupp, availableJobId, clm_no]);
|
||||
|
||||
if (error) return <AlertComponent type="error" message={error.message} />;
|
||||
|
||||
|
||||
return (
|
||||
<LoadingSpinner
|
||||
loading={insertLoading}
|
||||
@@ -374,11 +388,14 @@ export function JobsAvailableContainer({
|
||||
loading={estDataRaw.loading}
|
||||
error={estDataRaw.error}
|
||||
owner={owner}
|
||||
partsQueueToggle={partsQueueToggle}
|
||||
setPartsQueueToggle={setPartsQueueToggle}
|
||||
selectedOwner={selectedOwner}
|
||||
setSelectedOwner={setSelectedOwner}
|
||||
visible={ownerModalVisible}
|
||||
onOk={onOwnerFindModalOk}
|
||||
onCancel={onOwnerModalCancel}
|
||||
|
||||
/>
|
||||
<JobsFindModalContainer
|
||||
loading={estDataRaw.loading}
|
||||
@@ -390,6 +407,8 @@ export function JobsAvailableContainer({
|
||||
onOk={onJobFindModalOk}
|
||||
onCancel={onJobModalCancel}
|
||||
modalSearchState={modalSearchState}
|
||||
partsQueueToggle={partsQueueToggle}
|
||||
setPartsQueueToggle={setPartsQueueToggle}
|
||||
/>
|
||||
{currentUser.email.includes("@rome.") ||
|
||||
currentUser.email.includes("@imex.") ? (
|
||||
|
||||
@@ -108,7 +108,7 @@ export function JobsConvertButton({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
<Select showSearch>
|
||||
{bodyshop.md_ins_cos.map((s, i) => (
|
||||
<Select.Option key={i} value={s.name}>
|
||||
{s.name}
|
||||
|
||||
@@ -145,6 +145,13 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
||||
<DateTimePicker disabled={true || jobRO} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.date_lost_sale")}
|
||||
name="date_lost_sale"
|
||||
>
|
||||
<DateTimePicker disabled={true || jobRO} />
|
||||
</Form.Item>
|
||||
</FormRow>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,12 +18,14 @@ import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.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 { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
|
||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||
@@ -52,6 +54,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export function JobsDetailHeaderActions({
|
||||
@@ -67,6 +71,7 @@ export function JobsDetailHeaderActions({
|
||||
setTimeTicketContext,
|
||||
setTimeTicketTaskContext,
|
||||
setCardPaymentContext,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
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) => {
|
||||
@@ -161,6 +174,7 @@ export function JobsDetailHeaderActions({
|
||||
scheduled_in: null,
|
||||
scheduled_completion: null,
|
||||
lost_sale_reason,
|
||||
date_lost_sale: new Date(),
|
||||
status: bodyshop.md_ro_statuses.default_imported,
|
||||
},
|
||||
},
|
||||
@@ -169,6 +183,11 @@ export function JobsDetailHeaderActions({
|
||||
notification["success"]({
|
||||
message: t("appointments.successes.canceled"),
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation:
|
||||
AuditTrailMapping.appointmentcancel(lost_sale_reason),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -27,6 +27,8 @@ export default async function DuplicateJob(
|
||||
delete existingJob.id;
|
||||
delete existingJob.createdat;
|
||||
delete existingJob.updatedat;
|
||||
delete existingJob.cieca_stl;
|
||||
delete existingJob.cieca_ttl;
|
||||
|
||||
const newJob = {
|
||||
...existingJob,
|
||||
@@ -81,6 +83,8 @@ export async function CreateIouForJob(
|
||||
delete existingJob.id;
|
||||
delete existingJob.createdat;
|
||||
delete existingJob.updatedat;
|
||||
delete existingJob.cieca_stl;
|
||||
delete existingJob.cieca_ttl;
|
||||
|
||||
const newJob = {
|
||||
...existingJob,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
WarningFilled,
|
||||
BranchesOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
|
||||
import React, { useState } from "react";
|
||||
@@ -221,6 +221,14 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
<VehicleVinDisplay>
|
||||
{`${job.v_vin || t("general.labels.na")}`}
|
||||
</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 label={t("jobs.labels.relatedros")}>
|
||||
<JobsRelatedRos jobid={job.id} job={job} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { Gallery } from "react-grid-gallery";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GenerateThumbUrl } from "./job-documents.utility";
|
||||
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
|
||||
|
||||
function JobsDocumentGalleryExternal({
|
||||
data,
|
||||
@@ -15,7 +15,7 @@ function JobsDocumentGalleryExternal({
|
||||
let documents = data.reduce((acc, value) => {
|
||||
if (value.type.startsWith("image")) {
|
||||
acc.push({
|
||||
//src: GenerateSrcUrl(value),
|
||||
fullsize: GenerateSrcUrl(value),
|
||||
src: GenerateThumbUrl(value),
|
||||
thumbnailHeight: 225,
|
||||
thumbnailWidth: 225,
|
||||
|
||||
@@ -52,7 +52,7 @@ function JobDocumentsLocalGalleryExternal({
|
||||
val.type.mime &&
|
||||
val.type.mime.startsWith("image")
|
||||
) {
|
||||
acc.push({ ...val, src: val.thumbnail });
|
||||
acc.push({ ...val, src: val.thumbnail, fullsize: val.src });
|
||||
}
|
||||
return acc;
|
||||
}, [])
|
||||
|
||||
@@ -14,6 +14,8 @@ export default function JobsFindModalComponent({
|
||||
importOptionsState,
|
||||
modalSearchState,
|
||||
jobsListRefetch,
|
||||
partsQueueToggle,
|
||||
setPartsQueueToggle,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [modalSearch, setModalSearch] = modalSearchState;
|
||||
@@ -199,6 +201,12 @@ export default function JobsFindModalComponent({
|
||||
>
|
||||
{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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ export default connect(
|
||||
setSelectedJob,
|
||||
importOptionsState,
|
||||
modalSearchState,
|
||||
partsQueueToggle,
|
||||
setPartsQueueToggle,
|
||||
...modalProps
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
@@ -91,6 +93,8 @@ export default connect(
|
||||
jobsListRefetch={jobsList.refetch}
|
||||
jobsList={jobsData}
|
||||
modalSearchState={modalSearchState}
|
||||
partsQueueToggle={partsQueueToggle}
|
||||
setPartsQueueToggle={setPartsQueueToggle}
|
||||
/>
|
||||
) : null}
|
||||
</Modal>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -33,17 +34,14 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
|
||||
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder: sortcolumn === "ro_number" && sortorder,
|
||||
|
||||
render: (text, record) => (
|
||||
<Link to={"/manage/jobs/" + record.id}>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "ownr_ln",
|
||||
@@ -125,7 +123,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("vehicles.fields.plate_no"),
|
||||
dataIndex: "plate_no",
|
||||
key: "plate_no",
|
||||
|
||||
ellipsis: true,
|
||||
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
|
||||
sortOrder: sortcolumn === "plate_no" && sortorder,
|
||||
@@ -137,7 +134,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
title: t("jobs.fields.clm_no"),
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
|
||||
ellipsis: true,
|
||||
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sortOrder: sortcolumn === "clm_no" && sortorder,
|
||||
@@ -259,11 +255,11 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
pagination={
|
||||
search?.search
|
||||
? {
|
||||
pageSize: 25,
|
||||
pageSize: pageLimit,
|
||||
showSizeChanger: false,
|
||||
}
|
||||
: {
|
||||
pageSize: 25,
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
showSizeChanger: false,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
SyncOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
BranchesOutlined,
|
||||
SyncOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
BranchesOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
|
||||
@@ -22,374 +22,374 @@ import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export function JobsList({ bodyshop }) {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { selected } = searchParams;
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||
variables: {
|
||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const { selected } = searchParams;
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||
variables: {
|
||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" },
|
||||
});
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" },
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
const jobs = data
|
||||
? searchText === ""
|
||||
? data.jobs
|
||||
: data.jobs.filter(
|
||||
(j) =>
|
||||
(j.ro_number || "")
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_co_nm || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.comments || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_fn || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_ln || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.plate_no || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.v_model_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_fn || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_ln || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.v_make_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
const jobs = data
|
||||
? searchText === ""
|
||||
? data.jobs
|
||||
: data.jobs.filter(
|
||||
(j) =>
|
||||
(j.ro_number || "")
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_co_nm || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.comments || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_fn || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.ownr_ln || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(j.plate_no || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.v_model_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_fn || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.est_ct_ln || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase()) ||
|
||||
(j.v_make_desc || "")
|
||||
.toLowerCase()
|
||||
.includes(searchText.toLowerCase())
|
||||
)
|
||||
: [];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
const handleOnRowClick = (record) => {
|
||||
if (record) {
|
||||
if (record.id) {
|
||||
history.push({
|
||||
search: queryString.stringify({
|
||||
...searchParams,
|
||||
selected: record.id,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleOnRowClick = (record) => {
|
||||
if (record) {
|
||||
if (record.id) {
|
||||
history.push({
|
||||
search: queryString.stringify({
|
||||
...searchParams,
|
||||
selected: record.id,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) =>
|
||||
parseInt((a.ro_number || "0").replace(/\D/g, "")) -
|
||||
parseInt((b.ro_number || "0").replace(/\D/g, "")),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) =>
|
||||
parseInt((a.ro_number || "0").replace(/\D/g, "")) -
|
||||
parseInt((b.ro_number || "0").replace(/\D/g, "")),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.id}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
{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>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.id}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
{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>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
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.id} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
ellipsis: true,
|
||||
|
||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
filters:
|
||||
(jobs &&
|
||||
jobs
|
||||
.map((j) => j.status)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Status*",
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("vehicles.fields.plate_no"),
|
||||
dataIndex: "plate_no",
|
||||
key: "plate_no",
|
||||
ellipsis: true,
|
||||
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.clm_no"),
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
`${record.clm_no || ""}${
|
||||
record.po_number ? ` (PO: ${record.po_number})` : ""
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
filters:
|
||||
(jobs &&
|
||||
jobs
|
||||
.map((j) => j.ins_co_nm)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s,
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.clm_total"),
|
||||
dataIndex: "clm_total",
|
||||
key: "clm_total",
|
||||
responsive: ["md"],
|
||||
ellipsis: true,
|
||||
|
||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.estimator"),
|
||||
dataIndex: "jobs.labels.estimator",
|
||||
key: "jobs.labels.estimator",
|
||||
ellipsis: true,
|
||||
responsive: ["xl"],
|
||||
filterSearch: true,
|
||||
filters:
|
||||
(jobs &&
|
||||
jobs
|
||||
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "N/A",
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) =>
|
||||
value.includes(
|
||||
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
|
||||
),
|
||||
render: (text, record) =>
|
||||
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.comment"),
|
||||
dataIndex: "comment",
|
||||
key: "comment",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
// {
|
||||
// title: t("jobs.fields.owner_owing"),
|
||||
// dataIndex: "owner_owing",
|
||||
// key: "owner_owing",
|
||||
// responsive: ["md"],
|
||||
// render: (text, record) => (
|
||||
// <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
|
||||
// ),
|
||||
// },
|
||||
];
|
||||
|
||||
const scrollMapper = {
|
||||
xs: true,
|
||||
sm: true,
|
||||
md: true,
|
||||
lg: "100%",
|
||||
xl: "100%",
|
||||
xxl: "100%",
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("titles.bc.jobs-active")}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
onChange={(e) => {
|
||||
setSearchText(e.target.value);
|
||||
}}
|
||||
value={searchText}
|
||||
enterButton
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
loading={loading}
|
||||
pagination={{ defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={jobs}
|
||||
scroll={{
|
||||
x: selectedBreakpoint ? scrollMapper[selectedBreakpoint[0]] : "100%",
|
||||
}}
|
||||
rowSelection={{
|
||||
onSelect: (record) => {
|
||||
handleOnRowClick(record);
|
||||
},
|
||||
selectedRowKeys: [selected],
|
||||
type: "radio",
|
||||
}}
|
||||
onChange={handleTableChange}
|
||||
onRow={(record, rowIndex) => {
|
||||
return {
|
||||
onClick: (event) => {
|
||||
handleOnRowClick(record);
|
||||
);
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
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.id} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
ellipsis: true,
|
||||
|
||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
filters:
|
||||
(jobs &&
|
||||
jobs
|
||||
.map((j) => j.status)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Status*",
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("vehicles.fields.plate_no"),
|
||||
dataIndex: "plate_no",
|
||||
key: "plate_no",
|
||||
ellipsis: true,
|
||||
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.clm_no"),
|
||||
dataIndex: "clm_no",
|
||||
key: "clm_no",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
`${record.clm_no || ""}${
|
||||
record.po_number ? ` (PO: ${record.po_number})` : ""
|
||||
}`,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
filters:
|
||||
(jobs &&
|
||||
jobs
|
||||
.map((j) => j.ins_co_nm)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s,
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.clm_total"),
|
||||
dataIndex: "clm_total",
|
||||
key: "clm_total",
|
||||
responsive: ["md"],
|
||||
ellipsis: true,
|
||||
|
||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.estimator"),
|
||||
dataIndex: "jobs.labels.estimator",
|
||||
key: "jobs.labels.estimator",
|
||||
ellipsis: true,
|
||||
responsive: ["xl"],
|
||||
filterSearch: true,
|
||||
filters:
|
||||
(jobs &&
|
||||
jobs
|
||||
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "N/A",
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) =>
|
||||
value.includes(
|
||||
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
|
||||
),
|
||||
render: (text, record) =>
|
||||
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.comment"),
|
||||
dataIndex: "comment",
|
||||
key: "comment",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
// {
|
||||
// title: t("jobs.fields.owner_owing"),
|
||||
// dataIndex: "owner_owing",
|
||||
// key: "owner_owing",
|
||||
// responsive: ["md"],
|
||||
// render: (text, record) => (
|
||||
// <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
|
||||
// ),
|
||||
// },
|
||||
];
|
||||
|
||||
const scrollMapper = {
|
||||
xs: true,
|
||||
sm: true,
|
||||
md: true,
|
||||
lg: "100%",
|
||||
xl: "100%",
|
||||
xxl: "100%",
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("titles.bc.jobs-active")}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
onChange={(e) => {
|
||||
setSearchText(e.target.value);
|
||||
}}
|
||||
value={searchText}
|
||||
enterButton
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
loading={loading}
|
||||
pagination={{ defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={jobs}
|
||||
scroll={{
|
||||
x: selectedBreakpoint ? scrollMapper[selectedBreakpoint[0]] : "100%",
|
||||
}}
|
||||
rowSelection={{
|
||||
onSelect: (record) => {
|
||||
handleOnRowClick(record);
|
||||
},
|
||||
selectedRowKeys: [selected],
|
||||
type: "radio",
|
||||
}}
|
||||
onChange={handleTableChange}
|
||||
onRow={(record, rowIndex) => {
|
||||
return {
|
||||
onClick: (event) => {
|
||||
handleOnRowClick(record);
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(JobsList);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
SyncOutlined,
|
||||
BranchesOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
|
||||
@@ -14,12 +14,13 @@ import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -46,6 +47,7 @@ export function JobsReadyList({ bodyshop }) {
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||
variables: {
|
||||
statuses: readyStatuses,
|
||||
isConverted: true,
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
@@ -376,7 +378,7 @@ export function JobsReadyList({ bodyshop }) {
|
||||
>
|
||||
<Table
|
||||
loading={loading}
|
||||
pagination={{ defaultPageSize: 50 }}
|
||||
pagination={{ defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={jobs}
|
||||
|
||||
@@ -231,7 +231,14 @@ export function LaborAllocationsTable({
|
||||
{summary.adjustments.toFixed(1)}
|
||||
</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.Row>
|
||||
)}
|
||||
|
||||
@@ -8,10 +8,11 @@ export default function OwnerFindModalComponent({
|
||||
setSelectedOwner,
|
||||
ownersListLoading,
|
||||
ownersList,
|
||||
partsQueueToggle,
|
||||
setPartsQueueToggle,
|
||||
}) {
|
||||
//setSelectedOwner is used to set the record id of the owner to use for adding the job.
|
||||
const { t } = useTranslation();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("owners.fields.ownr_ln"),
|
||||
@@ -109,6 +110,12 @@ export default function OwnerFindModalComponent({
|
||||
>
|
||||
{t("owners.labels.create_new")}
|
||||
</Checkbox>
|
||||
<Checkbox
|
||||
checked={partsQueueToggle}
|
||||
onChange={(e) => setPartsQueueToggle(e.target.checked)}
|
||||
>
|
||||
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
|
||||
</Checkbox>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ export default function OwnerFindModalContainer({
|
||||
owner,
|
||||
selectedOwner,
|
||||
setSelectedOwner,
|
||||
partsQueueToggle,
|
||||
setPartsQueueToggle,
|
||||
...modalProps
|
||||
}) {
|
||||
//use owner object to run query and find what possible owners there are.
|
||||
@@ -59,6 +61,8 @@ export default function OwnerFindModalContainer({
|
||||
selectedOwner={selectedOwner}
|
||||
setSelectedOwner={setSelectedOwner}
|
||||
ownersListLoading={ownersList.loading}
|
||||
partsQueueToggle={partsQueueToggle}
|
||||
setPartsQueueToggle={setPartsQueueToggle}
|
||||
ownersList={
|
||||
ownersList.data && ownersList.data.search_owners
|
||||
? ownersList.data.search_owners
|
||||
|
||||
@@ -21,7 +21,8 @@ const OwnerSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => {
|
||||
useLazyQuery(SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE);
|
||||
|
||||
const executeSearch = (v) => {
|
||||
callSearch(v);
|
||||
if (v && v.variables?.search !== "" && v.variables.search.length >= 2)
|
||||
callSearch(v);
|
||||
};
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function OwnersListComponent({
|
||||
loading,
|
||||
@@ -106,7 +107,11 @@ export default function OwnersListComponent({
|
||||
<Input.Search
|
||||
placeholder={search.search || t("general.labels.search")}
|
||||
onSearch={(value) => {
|
||||
search.search = value;
|
||||
if (value?.length >= 3) {
|
||||
search.search = value;
|
||||
} else {
|
||||
delete search.search;
|
||||
}
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}}
|
||||
enterButton
|
||||
@@ -118,7 +123,7 @@ export default function OwnersListComponent({
|
||||
loading={loading}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: 25,
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
}}
|
||||
|
||||
@@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component";
|
||||
import OwnersListComponent from "./owners-list.component";
|
||||
import queryString from "query-string";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function OwnersListContainer() {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
@@ -16,8 +17,8 @@ export default function OwnersListContainer() {
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
search: search || "",
|
||||
offset: page ? (page - 1) * 25 : 0,
|
||||
limit: 25,
|
||||
offset: page ? (page - 1) * pageLimit : 0,
|
||||
limit: pageLimit,
|
||||
order: [
|
||||
{
|
||||
[sortcolumn || "created_at"]: sortorder
|
||||
|
||||
@@ -18,6 +18,7 @@ import { alphaSort } from "../../utils/sorters";
|
||||
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -282,11 +283,11 @@ export function PaymentsListPaginated({
|
||||
pagination={
|
||||
search?.search
|
||||
? {
|
||||
pageSize: 25,
|
||||
pageSize: pageLimit,
|
||||
showSizeChanger: false,
|
||||
}
|
||||
: {
|
||||
pageSize: 25,
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
showSizeChanger: false,
|
||||
|
||||
@@ -20,9 +20,9 @@ import ProductionBoardCard from "../production-board-kanban-card/production-boar
|
||||
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
||||
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
|
||||
//import "@asseinfo/react-kanban/dist/styles.css";
|
||||
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
|
||||
import "./production-board-kanban.styles.scss";
|
||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
@@ -153,6 +153,18 @@ export function ProductionBoardKanbanComponent({
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const totalLAB = data
|
||||
.reduce(
|
||||
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const totalLAR = data
|
||||
.reduce(
|
||||
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
.slice(-1)[0];
|
||||
@@ -236,6 +248,14 @@ export function ProductionBoardKanbanComponent({
|
||||
title={t("dashboard.titles.productionhours")}
|
||||
value={totalHrs}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("dashboard.titles.labhours")}
|
||||
value={totalLAB}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("dashboard.titles.larhours")}
|
||||
value={totalLAR}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("appointments.labels.inproduction")}
|
||||
value={data && data.length}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { Button, Dropdown, Menu } from "antd";
|
||||
import dataSource from "./production-list-columns.data";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import dataSource from "./production-list-columns.data";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -24,6 +24,7 @@ export function ProductionColumnsComponent({
|
||||
columnState,
|
||||
technician,
|
||||
bodyshop,
|
||||
data,
|
||||
tableState,
|
||||
}) {
|
||||
const [columns, setColumns] = columnState;
|
||||
@@ -36,6 +37,7 @@ export function ProductionColumnsComponent({
|
||||
bodyshop,
|
||||
technician,
|
||||
state: tableState,
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
}).filter((i) => i.key === e.key),
|
||||
]);
|
||||
@@ -46,6 +48,7 @@ export function ProductionColumnsComponent({
|
||||
technician,
|
||||
state: tableState,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
data: data,
|
||||
});
|
||||
const menu = (
|
||||
<Menu
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
import { ExclamationCircleFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Dropdown, Menu } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
export default function ProductionListColumnAlert({ record }) {
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export function ProductionListColumnAlert({ record, insertAuditTrail }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||
@@ -27,6 +38,14 @@ export default function ProductionListColumnAlert({ record }) {
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
insertAuditTrail({
|
||||
jobid: record.id,
|
||||
operation: AuditTrailMapping.alertToggle(
|
||||
!!record.production_vars && !!record.production_vars.alert
|
||||
? !record.production_vars.alert
|
||||
: true
|
||||
),
|
||||
}).then(() => {
|
||||
if (record.refetch) record.refetch();
|
||||
});
|
||||
@@ -58,3 +77,8 @@ export default function ProductionListColumnAlert({ record }) {
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProductionListColumnAlert);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PauseCircleOutlined, BranchesOutlined } from "@ant-design/icons";
|
||||
import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
|
||||
import { Space, Tooltip } from "antd";
|
||||
import i18n from "i18next";
|
||||
import moment from "moment";
|
||||
@@ -6,6 +6,7 @@ import { Link } from "react-router-dom";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { TimeFormatter } from "../../utils/DateFormatter";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||
@@ -27,7 +28,7 @@ import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.co
|
||||
import { store } from "../../redux/store";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
|
||||
const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
|
||||
return [
|
||||
{
|
||||
title: i18n.t("jobs.actions.viewdetail"),
|
||||
@@ -480,8 +481,8 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees.find((e) => e.id === a.employee_body)?.first_name,
|
||||
bodyshop.employees.find((e) => e.id === b.employee_body)?.first_name
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment
|
||||
@@ -499,8 +500,8 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees.find((e) => e.id === a.employee_prep)?.first_name,
|
||||
bodyshop.employees.find((e) => e.id === b.employee_prep)?.first_name
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment
|
||||
@@ -517,8 +518,8 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees.find((e) => e.id === a.employee_csr)?.first_name,
|
||||
bodyshop.employees.find((e) => e.id === b.employee_csr)?.first_name
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment record={record} type="employee_csr" />
|
||||
@@ -533,9 +534,9 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees.find((e) => e.id === a.employee_refinish)
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_refinish)
|
||||
?.first_name,
|
||||
bodyshop.employees.find((e) => e.id === b.employee_refinish)
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_refinish)
|
||||
?.first_name
|
||||
),
|
||||
render: (text, record) => (
|
||||
@@ -561,6 +562,36 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||
<JobPartsQueueCount parts={record.joblines_status} record={record} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.t("jobs.labels.estimator"),
|
||||
dataIndex: "estimator",
|
||||
key: "estimator",
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(),
|
||||
`${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim()
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order,
|
||||
filters:
|
||||
(data &&
|
||||
data
|
||||
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "N/A",
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) =>
|
||||
value.includes(
|
||||
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
|
||||
),
|
||||
render: (text, record) =>
|
||||
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
|
||||
},
|
||||
|
||||
//Added as a place holder for St Claude. Not implemented as it requires another join for a field used by only 1 client.
|
||||
// {
|
||||
|
||||
@@ -55,25 +55,27 @@ export function ProductionListPrint({ bodyshop }) {
|
||||
<Menu.SubMenu
|
||||
title={t("reportcenter.templates.production_by_technician_one")}
|
||||
>
|
||||
{bodyshop.employees.map((e) => (
|
||||
<Menu.Item
|
||||
key={e.id}
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
await GenerateDocument(
|
||||
{
|
||||
name: production_by_technician_one.key,
|
||||
variables: { id: e.id },
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
);
|
||||
setLoading(false);
|
||||
}}
|
||||
>
|
||||
{e.first_name} {e.last_name}
|
||||
</Menu.Item>
|
||||
))}
|
||||
{bodyshop.employees
|
||||
.filter((e) => e.active)
|
||||
.map((e) => (
|
||||
<Menu.Item
|
||||
key={e.id}
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
await GenerateDocument(
|
||||
{
|
||||
name: production_by_technician_one.key,
|
||||
variables: { id: e.id },
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
);
|
||||
setLoading(false);
|
||||
}}
|
||||
>
|
||||
{e.first_name} {e.last_name}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu
|
||||
title={t("reportcenter.templates.production_by_category_one")}
|
||||
|
||||
@@ -24,6 +24,7 @@ export function ProductionListTable({
|
||||
technician,
|
||||
currentUser,
|
||||
state,
|
||||
data,
|
||||
setColumns,
|
||||
setState,
|
||||
}) {
|
||||
@@ -41,6 +42,7 @@ export function ProductionListTable({
|
||||
bodyshop,
|
||||
technician,
|
||||
state,
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width,
|
||||
@@ -95,6 +97,7 @@ export function ProductionListTable({
|
||||
...ProductionListColumns({
|
||||
technician,
|
||||
state,
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Statistic,
|
||||
Table,
|
||||
} from "antd";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import ReactDragListView from "react-drag-listview";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -79,6 +79,7 @@ export function ProductionListTable({
|
||||
bodyshop,
|
||||
technician,
|
||||
state,
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width ?? 100,
|
||||
@@ -87,6 +88,33 @@ export function ProductionListTable({
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const newColumns =
|
||||
(state &&
|
||||
matchingColumnConfig &&
|
||||
matchingColumnConfig.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({
|
||||
bodyshop,
|
||||
technician,
|
||||
state,
|
||||
data: data,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width ?? 100,
|
||||
};
|
||||
})) ||
|
||||
[];
|
||||
setColumns(newColumns);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
//state,
|
||||
matchingColumnConfig,
|
||||
bodyshop,
|
||||
technician,
|
||||
data,
|
||||
]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed.
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({
|
||||
...state,
|
||||
@@ -104,7 +132,8 @@ export function ProductionListTable({
|
||||
|
||||
const removeColumn = (e) => {
|
||||
const { key } = e;
|
||||
setColumns(columns.filter((i) => i.key !== key));
|
||||
const newColumns = columns.filter((i) => i.key !== key);
|
||||
setColumns(newColumns);
|
||||
};
|
||||
|
||||
const handleResize =
|
||||
@@ -184,6 +213,18 @@ export function ProductionListTable({
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const totalLAB = data
|
||||
.reduce(
|
||||
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
const totalLAR = data
|
||||
.reduce(
|
||||
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
@@ -193,6 +234,14 @@ export function ProductionListTable({
|
||||
title={t("dashboard.titles.productionhours")}
|
||||
value={totalHrs}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("dashboard.titles.labhours")}
|
||||
value={totalLAB}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("dashboard.titles.larhours")}
|
||||
value={totalLAR}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("appointments.labels.inproduction")}
|
||||
value={dataSource && dataSource.length}
|
||||
@@ -207,6 +256,7 @@ export function ProductionListTable({
|
||||
<ProductionListColumnsAdd
|
||||
columnState={[columns, setColumns]}
|
||||
tableState={state}
|
||||
data={data}
|
||||
/>
|
||||
<ProductionListSaveConfigButton
|
||||
columns={columns}
|
||||
@@ -217,6 +267,7 @@ export function ProductionListTable({
|
||||
state={state}
|
||||
setState={setState}
|
||||
setColumns={setColumns}
|
||||
data={data}
|
||||
/>
|
||||
|
||||
<Input
|
||||
|
||||
@@ -55,10 +55,11 @@ const ret = {
|
||||
"shiftclock:view": 2,
|
||||
|
||||
"shop:config": 4,
|
||||
"shop:rbac": 5,
|
||||
"shop:vendors": 2,
|
||||
"shop:dashboard": 3,
|
||||
"shop:rbac": 5,
|
||||
"shop:reportcenter": 2,
|
||||
"shop:templates": 4,
|
||||
"shop:vendors": 2,
|
||||
|
||||
"temporarydocs:view": 2,
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectReportCenter } from "../../redux/modals/modals.selectors";
|
||||
import RbacWrapperComponent from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import ReportCenterModalComponent from "./report-center-modal.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -33,7 +34,9 @@ export function ReportCenterModalContainer({
|
||||
destroyOnClose
|
||||
width="80%"
|
||||
>
|
||||
<ReportCenterModalComponent />
|
||||
<RbacWrapperComponent action="shop:reportcenter">
|
||||
<ReportCenterModalComponent />
|
||||
</RbacWrapperComponent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,20 +13,29 @@ import {
|
||||
QUERY_APPOINTMENTS_BY_JOBID,
|
||||
} from "../../graphql/appointments.queries";
|
||||
import { QUERY_LBR_HRS_BY_PK, UPDATE_JOBS } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectSchedule } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { DateTimeFormat } from "../../utils/DateFormatter";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import ScheduleJobModalComponent from "./schedule-job-modal.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
scheduleModal: selectSchedule,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("schedule")),
|
||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export function ScheduleJobModalContainer({
|
||||
@@ -34,6 +43,8 @@ export function ScheduleJobModalContainer({
|
||||
bodyshop,
|
||||
toggleModalVisible,
|
||||
setEmailOptions,
|
||||
currentUser,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const { visible, context, actions } = scheduleModal;
|
||||
const { jobId, job, previousEvent } = context;
|
||||
@@ -122,12 +133,22 @@ export function ScheduleJobModalContainer({
|
||||
end: moment(values.start).add(bodyshop.appt_length || 60, "minutes"),
|
||||
color: values.color,
|
||||
note: values.note,
|
||||
created_by: currentUser.email,
|
||||
},
|
||||
jobId: jobId,
|
||||
altTransport: values.alt_transport,
|
||||
},
|
||||
});
|
||||
|
||||
if (!appt.errors) {
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.appointmentinsert(
|
||||
DateTimeFormat(values.start)
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (!!appt.errors) {
|
||||
notification["error"]({
|
||||
message: t("appointments.errors.saving", {
|
||||
@@ -149,6 +170,7 @@ export function ScheduleJobModalContainer({
|
||||
scheduled_in: values.start,
|
||||
scheduled_completion: values.scheduled_completion,
|
||||
lost_sale_reason: null,
|
||||
date_lost_sale: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -10,13 +10,14 @@ import OwnerNameDisplay from "../owner-name-display/owner-name-display.component
|
||||
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
|
||||
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
export default function ScoreboardJobsList({ scoreBoardlist }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
visible: false,
|
||||
search: "",
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
pageSize: pageLimit,
|
||||
});
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(
|
||||
@@ -148,7 +149,7 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
|
||||
}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: state.pageSize || 10,
|
||||
pageSize: state.pageSize || pageLimit,
|
||||
current: state.current || 1,
|
||||
total: data ? data.scoreboard_aggregate.aggregate.count : 0,
|
||||
}}
|
||||
|
||||
@@ -8,18 +8,45 @@ export const CalculateWorkingDaysThisMonth = () => {
|
||||
return moment().endOf("month").businessDaysIntoMonth();
|
||||
};
|
||||
|
||||
export const CalculateWorkingDaysInPeriod = (start, end) => {
|
||||
return moment(start).businessDiff(moment(end));
|
||||
};
|
||||
|
||||
export const CalculateWorkingDaysAsOfToday = () => {
|
||||
return moment().businessDaysIntoMonth();
|
||||
};
|
||||
|
||||
export const CalculateWorkingDaysLastMonth = () => {
|
||||
return moment().subtract(1, "month").endOf("month").businessDaysIntoMonth();
|
||||
};
|
||||
|
||||
export const WeeklyTargetHrs = (dailyTargetHrs, bodyshop) => {
|
||||
return dailyTargetHrs * 5;
|
||||
return (
|
||||
dailyTargetHrs *
|
||||
CalculateWorkingDaysInPeriod(
|
||||
moment().startOf("week"),
|
||||
moment().endOf("week")
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const WeeklyTargetHrsInPeriod = (
|
||||
dailyTargetHrs,
|
||||
start,
|
||||
end,
|
||||
bodyshop
|
||||
) => {
|
||||
return dailyTargetHrs * CalculateWorkingDaysInPeriod(start, end);
|
||||
};
|
||||
|
||||
export const MonthlyTargetHrs = (dailyTargetHrs, bodyshop) => {
|
||||
return dailyTargetHrs * CalculateWorkingDaysThisMonth();
|
||||
};
|
||||
|
||||
export const LastMonthTargetHrs = (dailyTargetHrs, bodyshop) => {
|
||||
return dailyTargetHrs * CalculateWorkingDaysLastMonth();
|
||||
};
|
||||
|
||||
export const AsOfTodayTargetHrs = (dailyTargetHrs, bodyshop) => {
|
||||
return dailyTargetHrs * CalculateWorkingDaysAsOfToday();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
const CustomTooltip = ({ active, payload, label }) => {
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "white",
|
||||
border: "1px solid gray",
|
||||
padding: "0.5rem",
|
||||
}}
|
||||
>
|
||||
<p style={{ margin: "0" }}>{label}</p>
|
||||
{payload.map((data, index) => {
|
||||
return (
|
||||
<p style={{ margin: "10px 0", color: data.color }} key={index}>{`${
|
||||
data.name
|
||||
} : ${data.value.toFixed(1)}`}</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default CustomTooltip;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Card } from "antd";
|
||||
import React from "react";
|
||||
import {
|
||||
Area,
|
||||
CartesianGrid,
|
||||
ComposedChart,
|
||||
Legend,
|
||||
Line,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import CustomTooltip from "./chart-custom-tooltip";
|
||||
|
||||
const graphProps = {
|
||||
strokeWidth: 3,
|
||||
};
|
||||
|
||||
export default function ScoreboardTimeTicketsChart({ data, chartTitle }) {
|
||||
return (
|
||||
<Card title={chartTitle}>
|
||||
<ResponsiveContainer width="100%" height={275}>
|
||||
<ComposedChart
|
||||
data={data}
|
||||
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
||||
>
|
||||
<CartesianGrid stroke="#f5f5f5" />
|
||||
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
|
||||
<YAxis yAxisId="left" strokeWidth={graphProps.strokeWidth} />
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Legend />
|
||||
<Line
|
||||
name="Target Hours"
|
||||
type="monotone"
|
||||
dataKey="accTargetHrs"
|
||||
stroke="#ff7300"
|
||||
yAxisId="left"
|
||||
strokeWidth={graphProps.strokeWidth}
|
||||
/>
|
||||
|
||||
<Area
|
||||
type="monotone"
|
||||
name="MTD Hours"
|
||||
dataKey="accHrs"
|
||||
fill="lightblue"
|
||||
stroke="blue"
|
||||
yAxisId="left"
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Row } from "antd";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
import ScoreboardTimeTicketsChart from "./scoreboard-timetickets.chart.component";
|
||||
import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component";
|
||||
import ScoreboardTimeticketsTargetsTable from "./scoreboard-timetickets.targets-table.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScoreboardTimeTicketsStats);
|
||||
|
||||
export function ScoreboardTimeTicketsStats({ bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const startDate = moment().startOf("month");
|
||||
const endDate = moment().endOf("month");
|
||||
|
||||
const fixedPeriods = useMemo(() => {
|
||||
const endOfThisMonth = moment().endOf("month");
|
||||
const startofthisMonth = moment().startOf("month");
|
||||
|
||||
const endOfLastmonth = moment().subtract(1, "month").endOf("month");
|
||||
const startOfLastmonth = moment().subtract(1, "month").startOf("month");
|
||||
|
||||
const endOfThisWeek = moment().endOf("week");
|
||||
const startOfThisWeek = moment().startOf("week");
|
||||
|
||||
const endOfLastWeek = moment().subtract(1, "week").endOf("week");
|
||||
const startOfLastWeek = moment().subtract(1, "week").startOf("week");
|
||||
|
||||
const endOfPriorWeek = moment().subtract(2, "week").endOf("week");
|
||||
const startOfPriorWeek = moment().subtract(2, "week").startOf("week");
|
||||
|
||||
const allDates = [
|
||||
endOfThisMonth,
|
||||
startofthisMonth,
|
||||
endOfLastmonth,
|
||||
startOfLastmonth,
|
||||
endOfThisWeek,
|
||||
startOfThisWeek,
|
||||
endOfLastWeek,
|
||||
startOfLastWeek,
|
||||
endOfPriorWeek,
|
||||
startOfPriorWeek,
|
||||
];
|
||||
const start = moment.min(allDates);
|
||||
const end = moment.max(allDates);
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
endOfThisMonth,
|
||||
startofthisMonth,
|
||||
endOfLastmonth,
|
||||
startOfLastmonth,
|
||||
endOfThisWeek,
|
||||
startOfThisWeek,
|
||||
endOfLastWeek,
|
||||
startOfLastWeek,
|
||||
endOfPriorWeek,
|
||||
startOfPriorWeek,
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, {
|
||||
variables: {
|
||||
start: startDate.format("YYYY-MM-DD"),
|
||||
end: endDate.format("YYYY-MM-DD"),
|
||||
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
|
||||
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
|
||||
jobStart: startDate,
|
||||
jobEnd: endDate,
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
pollInterval: 60000,
|
||||
skip: !fixedPeriods,
|
||||
});
|
||||
|
||||
const calculatedData = useMemo(() => {
|
||||
if (!data) return [];
|
||||
const ret = {
|
||||
totalThisWeek: 0,
|
||||
totalThisWeekLAB: 0,
|
||||
totalThisWeekLAR: 0,
|
||||
totalLastWeek: 0,
|
||||
totalLastWeekLAB: 0,
|
||||
totalLastWeekLAR: 0,
|
||||
totalPriorWeek: 0,
|
||||
totalPriorWeekLAB: 0,
|
||||
totalPriorWeekLAR: 0,
|
||||
totalThisMonth: 0,
|
||||
totalThisMonthLAB: 0,
|
||||
totalThisMonthLAR: 0,
|
||||
totalLastMonth: 0,
|
||||
totalLastMonthLAB: 0,
|
||||
totalLastMonthLAR: 0,
|
||||
actualTotalOverPeriod: 0,
|
||||
actualTotalOverPeriodLAB: 0,
|
||||
actualTotalOverPeriodLAR: 0,
|
||||
totalEffieciencyOverPeriod: 0,
|
||||
totalEffieciencyOverPeriodLAB: 0,
|
||||
totalEffieciencyOverPeriodLAR: 0,
|
||||
seperatedThisWeek: {
|
||||
sunday: {
|
||||
total: 0,
|
||||
lab: 0,
|
||||
lar: 0,
|
||||
},
|
||||
monday: {
|
||||
total: 0,
|
||||
lab: 0,
|
||||
lar: 0,
|
||||
},
|
||||
tuesday: {
|
||||
total: 0,
|
||||
lab: 0,
|
||||
lar: 0,
|
||||
},
|
||||
wednesday: {
|
||||
total: 0,
|
||||
lab: 0,
|
||||
lar: 0,
|
||||
},
|
||||
thursday: {
|
||||
total: 0,
|
||||
lab: 0,
|
||||
lar: 0,
|
||||
},
|
||||
friday: {
|
||||
total: 0,
|
||||
lab: 0,
|
||||
lar: 0,
|
||||
},
|
||||
saturday: {
|
||||
total: 0,
|
||||
lab: 0,
|
||||
lar: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
data.fixedperiod.forEach((ticket) => {
|
||||
const ticketDate = moment(ticket.date);
|
||||
if (
|
||||
ticketDate.isBetween(
|
||||
fixedPeriods.startOfThisWeek,
|
||||
fixedPeriods.endOfThisWeek,
|
||||
undefined,
|
||||
"[]"
|
||||
)
|
||||
) {
|
||||
ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs;
|
||||
if (ticket.ciecacode !== "LAR")
|
||||
ret.totalThisWeekLAB = ret.totalThisWeekLAB + ticket.productivehrs;
|
||||
if (ticket.ciecacode === "LAR")
|
||||
ret.totalThisWeekLAR = ret.totalThisWeekLAR + ticket.productivehrs;
|
||||
|
||||
//Seperate out to Day of Week
|
||||
ret.seperatedThisWeek[
|
||||
moment(ticket.date).format("dddd").toLowerCase()
|
||||
].total =
|
||||
ret.seperatedThisWeek[
|
||||
moment(ticket.date).format("dddd").toLowerCase()
|
||||
].total + ticket.productivehrs;
|
||||
if (ticket.ciecacode !== "LAR")
|
||||
ret.seperatedThisWeek[
|
||||
moment(ticket.date).format("dddd").toLowerCase()
|
||||
].lab =
|
||||
ret.seperatedThisWeek[
|
||||
moment(ticket.date).format("dddd").toLowerCase()
|
||||
].lab + ticket.productivehrs;
|
||||
if (ticket.ciecacode === "LAR")
|
||||
ret.seperatedThisWeek[
|
||||
moment(ticket.date).format("dddd").toLowerCase()
|
||||
].lar =
|
||||
ret.seperatedThisWeek[
|
||||
moment(ticket.date).format("dddd").toLowerCase()
|
||||
].lar + ticket.productivehrs;
|
||||
} else if (
|
||||
ticketDate.isBetween(
|
||||
fixedPeriods.startOfLastWeek,
|
||||
fixedPeriods.endOfLastWeek,
|
||||
undefined,
|
||||
"[]"
|
||||
)
|
||||
) {
|
||||
ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs;
|
||||
if (ticket.ciecacode !== "LAR")
|
||||
ret.totalLastWeekLAB = ret.totalLastWeekLAB + ticket.productivehrs;
|
||||
if (ticket.ciecacode === "LAR")
|
||||
ret.totalLastWeekLAR = ret.totalLastWeekLAR + ticket.productivehrs;
|
||||
} else if (
|
||||
ticketDate.isBetween(
|
||||
fixedPeriods.startOfPriorWeek,
|
||||
fixedPeriods.endOfPriorWeek,
|
||||
undefined,
|
||||
"[]"
|
||||
)
|
||||
) {
|
||||
ret.totalPriorWeek = ret.totalPriorWeek + ticket.productivehrs;
|
||||
if (ticket.ciecacode !== "LAR")
|
||||
ret.totalPriorWeekLAB = ret.totalPriorWeekLAB + ticket.productivehrs;
|
||||
if (ticket.ciecacode === "LAR")
|
||||
ret.totalPriorWeekLAR = ret.totalPriorWeekLAR + ticket.productivehrs;
|
||||
}
|
||||
if (
|
||||
ticketDate.isBetween(
|
||||
fixedPeriods.startofthisMonth,
|
||||
fixedPeriods.endOfThisMonth,
|
||||
undefined,
|
||||
"[]"
|
||||
)
|
||||
) {
|
||||
ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs;
|
||||
ret.actualTotalOverPeriod =
|
||||
ret.actualTotalOverPeriod + (ticket.actualhrs || 0);
|
||||
if (ticket.ciecacode !== "LAR") {
|
||||
ret.totalThisMonthLAB = ret.totalThisMonthLAB + ticket.productivehrs;
|
||||
ret.actualTotalOverPeriodLAB =
|
||||
ret.actualTotalOverPeriodLAB + (ticket.actualhrs || 0);
|
||||
}
|
||||
if (ticket.ciecacode === "LAR") {
|
||||
ret.totalThisMonthLAR = ret.totalThisMonthLAR + ticket.productivehrs;
|
||||
ret.actualTotalOverPeriodLAR =
|
||||
ret.actualTotalOverPeriodLAR + (ticket.actualhrs || 0);
|
||||
}
|
||||
} else if (
|
||||
ticketDate.isBetween(
|
||||
fixedPeriods.startOfLastmonth,
|
||||
fixedPeriods.endOfLastmonth,
|
||||
undefined,
|
||||
"[]"
|
||||
)
|
||||
) {
|
||||
ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs;
|
||||
if (ticket.ciecacode !== "LAR")
|
||||
ret.totalLastMonthLAB = ret.totalLastMonthLAB + ticket.productivehrs;
|
||||
if (ticket.ciecacode === "LAR")
|
||||
ret.totalLastMonthLAR = ret.totalLastMonthLAR + ticket.productivehrs;
|
||||
}
|
||||
});
|
||||
|
||||
ret.totalEffieciencyOverPeriod = ret.actualTotalOverPeriod
|
||||
? (ret.totalThisMonth / ret.actualTotalOverPeriod) * 100
|
||||
: 0;
|
||||
ret.totalEffieciencyOverPeriodLAB = ret.actualTotalOverPeriodLAB
|
||||
? (ret.totalThisMonthLAB / ret.actualTotalOverPeriodLAB) * 100
|
||||
: 0;
|
||||
ret.totalEffieciencyOverPeriodLAR = ret.actualTotalOverPeriodLAR
|
||||
? (ret.totalThisMonthLAR / ret.actualTotalOverPeriodLAR) * 100
|
||||
: 0;
|
||||
|
||||
roundObject(ret);
|
||||
|
||||
const ticketsGroupedByDate = _.groupBy(data.timetickets, "date");
|
||||
|
||||
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
||||
|
||||
const combinedData = [],
|
||||
labData = [],
|
||||
larData = [];
|
||||
var acc_comb = 0;
|
||||
var acc_lab = 0;
|
||||
var acc_lar = 0;
|
||||
|
||||
listOfDays.forEach((day) => {
|
||||
const r = {
|
||||
date: moment(day).format("MM/DD"),
|
||||
actualhrs: 0,
|
||||
productivehrs: 0,
|
||||
};
|
||||
|
||||
const combined = {
|
||||
accTargetHrs: _.round(
|
||||
Utils.AsOfDateTargetHours(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
day
|
||||
) +
|
||||
(bodyshop.scoreboard_target.dailyBodyTarget +
|
||||
bodyshop.scoreboard_target.dailyPaintTarget),
|
||||
1
|
||||
),
|
||||
accHrs: 0,
|
||||
};
|
||||
const lab = {
|
||||
accTargetHrs: _.round(
|
||||
Utils.AsOfDateTargetHours(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
day
|
||||
) + bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
1
|
||||
),
|
||||
accHrs: 0,
|
||||
};
|
||||
const lar = {
|
||||
accTargetHrs: _.round(
|
||||
Utils.AsOfDateTargetHours(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
day
|
||||
) + bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
1
|
||||
),
|
||||
accHrs: 0,
|
||||
};
|
||||
|
||||
if (ticketsGroupedByDate[day]) {
|
||||
ticketsGroupedByDate[day].forEach((ticket) => {
|
||||
r.actualhrs = r.actualhrs + ticket.actualhrs;
|
||||
r.productivehrs = r.productivehrs + ticket.productivehrs;
|
||||
acc_comb = acc_comb + ticket.productivehrs;
|
||||
|
||||
if (ticket.ciecacode !== "LAR")
|
||||
acc_lab = acc_lab + ticket.productivehrs;
|
||||
if (ticket.ciecacode === "LAR")
|
||||
acc_lar = acc_lar + ticket.productivehrs;
|
||||
});
|
||||
}
|
||||
combined.accHrs = acc_comb;
|
||||
lab.accHrs = acc_lab;
|
||||
lar.accHrs = acc_lar;
|
||||
|
||||
combinedData.push({ ...r, ...combined });
|
||||
labData.push({ ...r, ...lab });
|
||||
larData.push({ ...r, ...lar });
|
||||
});
|
||||
|
||||
const jobData = {};
|
||||
data.jobs.forEach((job) => {
|
||||
job.tthrs = job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
||||
});
|
||||
jobData.tthrs = data.jobs
|
||||
.reduce((acc, val) => acc + val.tthrs, 0)
|
||||
.toFixed(1);
|
||||
jobData.count = data.jobs.length.toFixed(0);
|
||||
|
||||
return {
|
||||
fixed: ret,
|
||||
combinedData: combinedData,
|
||||
labData: labData,
|
||||
larData: larData,
|
||||
jobData: jobData,
|
||||
};
|
||||
}, [fixedPeriods, data, bodyshop]);
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (loading) return <LoadingSpinner />;
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<ScoreboardTimeticketsTargetsTable />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ScoreboardTicketsStats
|
||||
data={calculatedData.fixed}
|
||||
jobData={calculatedData.jobData}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<ScoreboardTimeTicketsChart
|
||||
data={calculatedData.combinedData}
|
||||
chartTitle={t("scoreboard.labels.combinedcharttitle")}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<ScoreboardTimeTicketsChart
|
||||
data={calculatedData.labData}
|
||||
chartTitle={t("scoreboard.labels.bodycharttitle")}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<ScoreboardTimeTicketsChart
|
||||
data={calculatedData.larData}
|
||||
chartTitle={t("scoreboard.labels.refinishcharttitle")}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function roundObject(inputObj) {
|
||||
for (var key of Object.keys(inputObj)) {
|
||||
if (typeof inputObj[key] === "number") {
|
||||
inputObj[key] = inputObj[key].toFixed(1);
|
||||
} else if (Array.isArray(inputObj[key])) {
|
||||
inputObj[key].forEach((item) => roundObject(item));
|
||||
} else if (typeof inputObj[key] === "object") {
|
||||
roundObject(inputObj[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,651 @@
|
||||
import {
|
||||
Card,
|
||||
Col,
|
||||
Form,
|
||||
Row,
|
||||
Space,
|
||||
Statistic,
|
||||
Switch,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import * as Util from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScoreboardTicketsStats);
|
||||
|
||||
function useLocalStorage(key, initialValue) {
|
||||
const [storedValue, setStoredValue] = useState(() => {
|
||||
const item = localStorage.getItem(key);
|
||||
return item ? JSON.parse(item) : initialValue;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem(key, JSON.stringify(storedValue));
|
||||
}, [key, storedValue]);
|
||||
|
||||
return [storedValue, setStoredValue];
|
||||
}
|
||||
|
||||
export function ScoreboardTicketsStats({ data, jobData, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false);
|
||||
|
||||
const statisticSize = isLarge ? 36 : 24;
|
||||
const statisticWeight = isLarge ? 550 : "normal";
|
||||
const daySpan =
|
||||
Util.CalculateWorkingDaysInPeriod(
|
||||
moment().startOf("week"),
|
||||
moment().endOf("week")
|
||||
) > 5
|
||||
? 3
|
||||
: 4;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("scoreboard.labels.productivestatistics")}
|
||||
extra={
|
||||
<Form.Item
|
||||
label={t("general.labels.tvmode")}
|
||||
valuePropName="checked"
|
||||
name={["tvmode"]}
|
||||
>
|
||||
<Switch
|
||||
onClick={() => setIsLarge(!isLarge)}
|
||||
defaultChecked={isLarge}
|
||||
/>
|
||||
</Form.Item>
|
||||
}
|
||||
>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col md={24}>
|
||||
{/* Daily Stats */}
|
||||
<Space direction="vertical" size="middle" style={{ display: "flex" }}>
|
||||
<Row gutter={[16, 16]} align="center">
|
||||
{[
|
||||
"sunday",
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
].map((day) => {
|
||||
if (bodyshop.workingdays[day] === true) {
|
||||
return (
|
||||
<Col key={day} span={daySpan} align="center">
|
||||
<Card size="small" title={t("general.labels." + day)}>
|
||||
<Row gutter={[8, 8]}>
|
||||
<Col span={24}>
|
||||
<Statistic
|
||||
value={data.seperatedThisWeek[day].total}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(
|
||||
data.seperatedThisWeek[day].total
|
||||
) >=
|
||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||
bodyshop.scoreboard_target.dailyPaintTarget
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.body")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.seperatedThisWeek[day].lab}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.seperatedThisWeek[day].lab) >=
|
||||
bodyshop.scoreboard_target.dailyBodyTarget
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.refinish")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.seperatedThisWeek[day].lar}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.seperatedThisWeek[day].lar) >=
|
||||
bodyshop.scoreboard_target.dailyPaintTarget
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
</Row>
|
||||
{/* Weekly Stats */}
|
||||
<Row gutter={[16, 16]}>
|
||||
{/* This Week */}
|
||||
<Col span={8} align="center">
|
||||
<Card size="small" title={t("scoreboard.labels.thisweek")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Statistic
|
||||
value={data.totalThisWeek}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalThisWeek) >=
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().startOf("week"),
|
||||
moment().endOf("week"),
|
||||
bodyshop
|
||||
) +
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().startOf("week"),
|
||||
moment().endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.body")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.totalThisWeekLAB}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalThisWeekLAB) >=
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().startOf("week"),
|
||||
moment().endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.refinish")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.totalThisWeekLAR}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalThisWeekLAR) >=
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().startOf("week"),
|
||||
moment().endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
{/* Last Week */}
|
||||
<Col span={8} align="center">
|
||||
<Card size="small" title={t("scoreboard.labels.lastweek")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Statistic
|
||||
value={data.totalLastWeek}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalLastWeek) >=
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().subtract(1, "week").startOf("week"),
|
||||
moment().subtract(1, "week").endOf("week"),
|
||||
bodyshop
|
||||
) +
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().subtract(1, "week").startOf("week"),
|
||||
moment().subtract(1, "week").endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.body")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.totalLastWeekLAB}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalLastWeekLAB) >=
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().subtract(1, "week").startOf("week"),
|
||||
moment().subtract(1, "week").endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.refinish")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.totalLastWeekLAR}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalLastWeekLAR) >=
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().subtract(1, "week").startOf("week"),
|
||||
moment().subtract(1, "week").endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
{/* Prior Week */}
|
||||
<Col span={8} align="center">
|
||||
<Card size="small" title={t("scoreboard.labels.priorweek")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Statistic
|
||||
value={data.totalPriorWeek}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalPriorWeek) >=
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().subtract(2, "week").startOf("week"),
|
||||
moment().subtract(2, "week").endOf("week"),
|
||||
bodyshop
|
||||
) +
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().subtract(2, "week").startOf("week"),
|
||||
moment().subtract(2, "week").endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.body")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.totalPriorWeekLAB}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalPriorWeekLAB) >=
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().subtract(2, "week").startOf("week"),
|
||||
moment().subtract(2, "week").endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.refinish")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.totalPriorWeekLAR}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalPriorWeekLAR) >=
|
||||
Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().subtract(2, "week").startOf("week"),
|
||||
moment().subtract(2, "week").endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
{/* Monthly Stats */}
|
||||
<Row gutter={[16, 16]}>
|
||||
{/* This Month */}
|
||||
<Col span={7} align="center">
|
||||
<Card size="small" title={t("scoreboard.labels.thismonth")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Statistic
|
||||
value={data.totalThisMonth}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalThisMonth) >=
|
||||
Util.MonthlyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
bodyshop
|
||||
) +
|
||||
Util.MonthlyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.body")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.totalThisMonthLAB}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalThisMonthLAB) >=
|
||||
Util.MonthlyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.refinish")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.totalThisMonthLAR}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalThisMonthLAR) >=
|
||||
Util.MonthlyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
{/* Last Month */}
|
||||
<Col span={7} align="center">
|
||||
<Card size="small" title={t("scoreboard.labels.lastmonth")}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Statistic
|
||||
value={data.totalLastMonth}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalLastMonth) >=
|
||||
Util.LastMonthTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
bodyshop
|
||||
) +
|
||||
Util.LastMonthTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.body")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.totalLastMonthLAB}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalLastMonthLAB) >=
|
||||
Util.LastMonthTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.refinish")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={data.totalLastMonthLAR}
|
||||
valueStyle={{
|
||||
color:
|
||||
parseFloat(data.totalLastMonthLAR) >=
|
||||
Util.LastMonthTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
bodyshop
|
||||
)
|
||||
? "green"
|
||||
: "red",
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
{/* Efficiency Over Period */}
|
||||
<Col span={7} align="center">
|
||||
<Card
|
||||
size="small"
|
||||
title={t("scoreboard.labels.efficiencyoverperiod")}
|
||||
>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Statistic
|
||||
value={`${data.totalEffieciencyOverPeriod || 0}%`}
|
||||
valueStyle={{
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.body")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={`${data.totalEffieciencyOverPeriodLAB || 0}%`}
|
||||
valueStyle={{
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.refinish")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={`${data.totalEffieciencyOverPeriodLAR || 0}%`}
|
||||
valueStyle={{
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={3} align="center">
|
||||
<Card
|
||||
size="small"
|
||||
title={t("scoreboard.labels.jobscompletednotinvoiced")}
|
||||
>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Statistic
|
||||
value={jobData.count}
|
||||
valueStyle={{
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Statistic
|
||||
title={
|
||||
<Typography.Text strong>
|
||||
{t("scoreboard.labels.totalhrs")}
|
||||
</Typography.Text>
|
||||
}
|
||||
value={jobData.tthrs}
|
||||
valueStyle={{
|
||||
fontSize: statisticSize,
|
||||
fontWeight: statisticWeight,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Space>
|
||||
{/* Disclaimer */}
|
||||
<Typography.Text type="secondary">
|
||||
*{t("scoreboard.labels.calendarperiod")}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
import { CalendarOutlined } from "@ant-design/icons";
|
||||
import { Card, Col, Divider, Row, Statistic } from "antd";
|
||||
import moment from "moment";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import * as Util from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
const rowGutter = [16, 16];
|
||||
const statSpans = { xs: 24, sm: 3 };
|
||||
|
||||
export function ScoreboardTimeticketsTargetsTable({ bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Card title={t("scoreboard.labels.targets")}>
|
||||
<Row gutter={rowGutter}>
|
||||
<Col xs={24} sm={{ offset: 0, span: 3 }} lg={{ span: 3 }}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.workingdays")}
|
||||
value={Util.CalculateWorkingDaysThisMonth()}
|
||||
prefix={<CalendarOutlined />}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={{ offset: 0, span: 20 }} lg={{ offset: 0, span: 20 }}>
|
||||
<Row>
|
||||
<Col {...statSpans}>
|
||||
<Statistic title="Type" value={t("scoreboard.labels.body")} />
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.dailytarget")}
|
||||
value={bodyshop.scoreboard_target.dailyBodyTarget}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.thisweek")}
|
||||
value={Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().startOf("week"),
|
||||
moment().endOf("week"),
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.lastweek")}
|
||||
value={Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().subtract(1, "week").startOf("week"),
|
||||
moment().subtract(1, "week").endOf("week"),
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.priorweek")}
|
||||
value={Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().subtract(2, "week").startOf("week"),
|
||||
moment().subtract(2, "week").endOf("week"),
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.thismonth")}
|
||||
value={Util.MonthlyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.lastmonth")}
|
||||
value={Util.LastMonthTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
title={t("scoreboard.labels.asoftodaytarget")}
|
||||
value={Util.AsOfTodayTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col {...statSpans}>
|
||||
<Statistic value={t("scoreboard.labels.refinish")} />
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic value={bodyshop.scoreboard_target.dailyPaintTarget} />
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={Util.WeeklyTargetHrsInPeriod(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().startOf("week"),
|
||||
moment().endOf("week"),
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().subtract(1, "week").startOf("week"),
|
||||
moment().subtract(1, "week").endOf("week"),
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().subtract(2, "week").startOf("week"),
|
||||
moment().subtract(2, "week").endOf("week"),
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={Util.MonthlyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={Util.LastMonthTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={Util.AsOfTodayTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
bodyshop
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Divider style={{ margin: 5 }} />
|
||||
</Row>
|
||||
<Row>
|
||||
<Col {...statSpans}></Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||
bodyshop.scoreboard_target.dailyPaintTarget
|
||||
).toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={(
|
||||
Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().startOf("week"),
|
||||
moment().endOf("week"),
|
||||
bodyshop
|
||||
) +
|
||||
Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().startOf("week"),
|
||||
moment().endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
).toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={(
|
||||
Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().subtract(1, "week").startOf("week"),
|
||||
moment().subtract(1, "week").endOf("week"),
|
||||
bodyshop
|
||||
) +
|
||||
Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().subtract(1, "week").startOf("week"),
|
||||
moment().subtract(1, "week").endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
).toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={(
|
||||
Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
moment().subtract(2, "week").startOf("week"),
|
||||
moment().subtract(2, "week").endOf("week"),
|
||||
bodyshop
|
||||
) +
|
||||
Util.WeeklyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
moment().subtract(2, "week").startOf("week"),
|
||||
moment().subtract(2, "week").endOf("week"),
|
||||
bodyshop
|
||||
)
|
||||
).toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={(
|
||||
Util.MonthlyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
bodyshop
|
||||
) +
|
||||
Util.MonthlyTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
bodyshop
|
||||
)
|
||||
).toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={(
|
||||
Util.LastMonthTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
bodyshop
|
||||
) +
|
||||
Util.LastMonthTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
bodyshop
|
||||
)
|
||||
).toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...statSpans}>
|
||||
<Statistic
|
||||
value={(
|
||||
Util.AsOfTodayTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget,
|
||||
bodyshop
|
||||
) +
|
||||
Util.AsOfTodayTargetHrs(
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
bodyshop
|
||||
)
|
||||
).toFixed(1)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScoreboardTimeticketsTargetsTable);
|
||||
@@ -65,6 +65,8 @@ export default function ScoreboardTimeTickets() {
|
||||
end: endDate.format("YYYY-MM-DD"),
|
||||
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
|
||||
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
|
||||
jobStart: startDate,
|
||||
jobEnd: endDate,
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
|
||||
@@ -42,6 +42,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<LayoutFormRow
|
||||
@@ -680,6 +681,13 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
|
||||
name={["md_functionality_toggles","parts_queue_toggle"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["last_name_first"]}
|
||||
label={t("bodyshop.fields.last_name_first")}
|
||||
|
||||
@@ -28,18 +28,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
return (
|
||||
<RbacWrapper action="shop:rbac">
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.payables")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "accounting:payables"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.exportlog")}
|
||||
rules={[
|
||||
@@ -52,6 +40,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.payables")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "accounting:payables"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.payments")}
|
||||
rules={[
|
||||
@@ -77,26 +77,62 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.csi.page")}
|
||||
label={t("bodyshop.fields.rbac.bills.delete")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "csi:page"]}
|
||||
name={["md_rbac", "bills:delete"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.csi.export")}
|
||||
label={t("bodyshop.fields.rbac.bills.enter")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "csi:export"]}
|
||||
name={["md_rbac", "bills:enter"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "bills:list"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.reexport")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "bills:reexport"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "bills:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
@@ -173,26 +209,38 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.list-active")}
|
||||
label={t("bodyshop.fields.rbac.csi.export")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:list-active"]}
|
||||
name={["md_rbac", "csi:export"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.list-ready")}
|
||||
label={t("bodyshop.fields.rbac.csi.page")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:list-ready"]}
|
||||
name={["md_rbac", "csi:page"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.employees.page")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "employees:page"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
@@ -208,30 +256,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.partsqueue")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:partsqueue"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.list-all")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:list-all"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.available-list")}
|
||||
rules={[
|
||||
@@ -245,26 +269,14 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.create")}
|
||||
label={t("bodyshop.fields.rbac.jobs.checklist-view")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:create"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.intake")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:intake"]}
|
||||
name={["md_rbac", "jobs:checklist-view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
@@ -280,6 +292,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.create")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:create"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.deliver")}
|
||||
rules={[
|
||||
@@ -305,14 +329,62 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.checklist-view")}
|
||||
label={t("bodyshop.fields.rbac.jobs.intake")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:checklist-view"]}
|
||||
name={["md_rbac", "jobs:intake"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.list-active")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:list-active"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.list-all")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:list-all"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.list-ready")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:list-ready"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.jobs.partsqueue")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "jobs:partsqueue"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
@@ -329,55 +401,7 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.enter")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "bills:enter"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.delete")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "bills:delete"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.reexport")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "bills:reexport"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "bills:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.list")}
|
||||
label={t("bodyshop.fields.rbac.owners.detail")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
@@ -424,18 +448,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.owners.detail")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "owners:detail"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.payments.enter")}
|
||||
rules={[
|
||||
@@ -460,6 +472,30 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.phonebook.edit")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "phonebook:edit"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.phonebook.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "phonebook:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.production.board")}
|
||||
rules={[
|
||||
@@ -521,7 +557,7 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.edit")}
|
||||
label={t("bodyshop.fields.rbac.shop.config")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
@@ -604,18 +640,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.config")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "shop:config"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.rbac")}
|
||||
rules={[
|
||||
@@ -628,6 +652,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.reportcenter")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "shop:reportcenter"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.templates")}
|
||||
rules={[
|
||||
@@ -640,6 +676,42 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.vendors")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "shop:vendors"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.temporarydocs.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "temporarydocs:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.edit")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "timetickets:edit"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.enter")}
|
||||
rules={[
|
||||
@@ -664,6 +736,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "timetickets:shiftedit"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.users.editaccess")}
|
||||
rules={[
|
||||
@@ -676,42 +760,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.temporarydocs.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "temporarydocs:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.phonebook.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "phonebook:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.phonebook.edit")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "phonebook:edit"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
{Simple_Inventory.treatment === "on" && (
|
||||
<>
|
||||
<Form.Item
|
||||
|
||||
@@ -2,11 +2,11 @@ import { DeleteFilled } from "@ant-design/icons";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Space,
|
||||
Card,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Space,
|
||||
Switch,
|
||||
notification,
|
||||
} from "antd";
|
||||
@@ -157,6 +157,18 @@ export function ShopEmployeeTeamsFormComponent({ bodyshop }) {
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("employee_teams.fields.max_load")}
|
||||
name="max_load"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={1} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Form.List name={["employee_team_members"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
@@ -199,7 +211,7 @@ export function ShopEmployeeTeamsFormComponent({ bodyshop }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={2}/>
|
||||
<InputNumber min={0} max={100} precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAA")}
|
||||
|
||||
@@ -25,6 +25,7 @@ export function TechLookupJobsList({ bodyshop }) {
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||
variables: {
|
||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
|
||||
isConverted: true,
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
|
||||
@@ -16,13 +16,12 @@ const VehicleSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => {
|
||||
SEARCH_VEHICLES_FOR_AUTOCOMPLETE
|
||||
);
|
||||
|
||||
const [
|
||||
callIdSearch,
|
||||
{ loading: idLoading, error: idError, data: idData },
|
||||
] = useLazyQuery(SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE);
|
||||
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] =
|
||||
useLazyQuery(SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE);
|
||||
|
||||
const executeSearch = (v) => {
|
||||
callSearch(v);
|
||||
if (v && v.variables?.search !== "" && v.variables.search.length >= 2)
|
||||
callSearch(v);
|
||||
};
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user