Compare commits
467 Commits
feature/IO
...
rome/test
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82fbe74723 | ||
|
|
1f2da44a3f | ||
|
|
85a3aeb335 | ||
|
|
80b7ae0e54 | ||
|
|
0529ac4478 | ||
|
|
3cafbebbee | ||
|
|
6f248d864e | ||
|
|
a4a84572b7 | ||
|
|
a45d0bb9f4 | ||
|
|
a2e0f9fbe7 | ||
|
|
e37dc0a18f | ||
|
|
0bc00d46cf | ||
|
|
e80e40bb76 | ||
|
|
a88c102b27 | ||
|
|
c691d44c44 | ||
|
|
ebb3a13ff5 | ||
|
|
3846b7c5fc | ||
|
|
93d139f926 | ||
|
|
4d1f40537c | ||
|
|
896f1415f7 | ||
|
|
8a01cd9cb0 | ||
|
|
c2013d47e7 | ||
|
|
0f20807690 | ||
|
|
2a45be6a45 | ||
|
|
b63602143e | ||
|
|
2add712270 | ||
|
|
77c8f74bcb | ||
|
|
1d38102541 | ||
|
|
6117b5ab64 | ||
|
|
578f0a110e | ||
|
|
b5c66274ca | ||
|
|
be46bdc57f | ||
|
|
6263e63a1d | ||
|
|
83d702f12b | ||
|
|
37708a0b59 | ||
|
|
6cfcab8156 | ||
|
|
67bcae3c5b | ||
|
|
da65f39d28 | ||
|
|
929854fd55 | ||
|
|
9e216bb3ad | ||
|
|
4bfee3c135 | ||
|
|
6187b19e91 | ||
|
|
54df4a7f8c | ||
|
|
33ec18986d | ||
|
|
6b7b34ae79 | ||
|
|
06ef2482ba | ||
|
|
83bd485597 | ||
|
|
6921f2fe68 | ||
|
|
a7e199932c | ||
|
|
2427c14b7b | ||
|
|
66655d449e | ||
|
|
845a84c4c8 | ||
|
|
0ff5ea3d59 | ||
|
|
09492e647e | ||
|
|
2d6594cc73 | ||
|
|
3b8e83d88a | ||
|
|
3ec4dbb5b8 | ||
|
|
601fdbba39 | ||
|
|
830adc4ef3 | ||
|
|
3f216195ca | ||
|
|
bcb8de0937 | ||
|
|
9cc0d6175e | ||
|
|
4c07632ee6 | ||
|
|
767c219af8 | ||
|
|
cfc301570e | ||
|
|
fb718f9f37 | ||
|
|
cafc0e5628 | ||
|
|
a635725839 | ||
|
|
eb8e9b10ef | ||
|
|
2584f7129c | ||
|
|
771f75788a | ||
|
|
89bc11e4ad | ||
|
|
b8880e8cb8 | ||
|
|
6c996037d6 | ||
|
|
f421b45222 | ||
|
|
30cf46a158 | ||
|
|
8d37c54f89 | ||
|
|
f9b9f39418 | ||
|
|
05a5df789b | ||
|
|
bed87eda97 | ||
|
|
67008c35b8 | ||
|
|
9b95e40a13 | ||
|
|
e3a998b6f8 | ||
|
|
3c7ede0155 | ||
|
|
82c7aa1347 | ||
|
|
4ccd912363 | ||
|
|
c0525842fd | ||
|
|
dd5ca5d233 | ||
|
|
616a4b04a0 | ||
|
|
9a34640c88 | ||
|
|
3110be4703 | ||
|
|
971c8d41ff | ||
|
|
7c303a5154 | ||
|
|
9383b37a41 | ||
|
|
205d507097 | ||
|
|
97a1bd66d1 | ||
|
|
0d1ff6390c | ||
|
|
830d2c87d2 | ||
|
|
da98e7b886 | ||
|
|
a74a9ba5a1 | ||
|
|
b8a3081488 | ||
|
|
c8fc1b0f68 | ||
|
|
da1ddb874f | ||
|
|
771f9ceaa8 | ||
|
|
9b61da5c62 | ||
|
|
7daf7540b3 | ||
|
|
ce6940629d | ||
|
|
b706b96d32 | ||
|
|
2427bc72f2 | ||
|
|
8bc1a9d9ee | ||
|
|
618107fc63 | ||
|
|
0879cb602d | ||
|
|
d5198edfc0 | ||
|
|
647c796497 | ||
|
|
39bba3623a | ||
|
|
8687aeb9bf | ||
|
|
62dac2193f | ||
|
|
22feaf6c77 | ||
|
|
72da0734c8 | ||
|
|
e72f42bda2 | ||
|
|
69b4b76501 | ||
|
|
ae56e27e5f | ||
|
|
12d0a9ec78 | ||
|
|
d6bb6a891d | ||
|
|
ba30225ba1 | ||
|
|
cb4a6e8774 | ||
|
|
2f83b0baa7 | ||
|
|
23f640028d | ||
|
|
04728690ca | ||
|
|
3e5e6263fe | ||
|
|
48cef3e188 | ||
|
|
8c0d6b2f6b | ||
|
|
22ee8acd0d | ||
|
|
afdcfb7bf6 | ||
|
|
c1f6d06128 | ||
|
|
120a8a4576 | ||
|
|
89224e871c | ||
|
|
fa0d472fb6 | ||
|
|
b0f4ad7e4f | ||
|
|
7503d86c69 | ||
|
|
efd1c17033 | ||
|
|
c7a0072f2d | ||
|
|
908942ec09 | ||
|
|
e4d3b53349 | ||
|
|
2a31d740d5 | ||
|
|
03ce5458b5 | ||
|
|
61c03ee206 | ||
|
|
0e4f5b8b2a | ||
|
|
b5d4944ad8 | ||
|
|
50f84d40e1 | ||
|
|
a394d6b37e | ||
|
|
f8408908b2 | ||
|
|
eb8519dc1d | ||
|
|
36dd97394f | ||
|
|
03d4e4dcd1 | ||
|
|
6489a8666f | ||
|
|
5ea64ed805 | ||
|
|
d740446ccb | ||
|
|
d0a2bb7da0 | ||
|
|
5de4ef5d83 | ||
|
|
f59bdf9030 | ||
|
|
cfe0727447 | ||
|
|
09d112350a | ||
|
|
52f8eabd2b | ||
|
|
a162b275a3 | ||
|
|
2e7232bb65 | ||
|
|
82dc9e1c56 | ||
|
|
272a3f579a | ||
|
|
ff1ceb20cb | ||
|
|
343179d4fe | ||
|
|
eabbc2211b | ||
|
|
7f587680ca | ||
|
|
d61ed03ef1 | ||
|
|
166efdc877 | ||
|
|
ff7523ecb8 | ||
|
|
b53fdadaee | ||
|
|
93390bef63 | ||
|
|
485e2ef4f1 | ||
|
|
9de3b5efb6 | ||
|
|
2632282bba | ||
|
|
7379f50792 | ||
|
|
9ae53d090f | ||
|
|
677c7bb4cc | ||
|
|
4fdc241b9c | ||
|
|
06ca7603b9 | ||
|
|
28dc4867db | ||
|
|
1cf13da413 | ||
|
|
3919efaa77 | ||
|
|
a76fc780cb | ||
|
|
d915c123db | ||
|
|
151c9c0add | ||
|
|
f14dc497cf | ||
|
|
89339e1033 | ||
|
|
146a61c9cf | ||
|
|
5250d97935 | ||
|
|
18dce9795d | ||
|
|
7add2bb12e | ||
|
|
756fc07193 | ||
|
|
71a5722b0e | ||
|
|
4507135a1b | ||
|
|
7f2479f597 | ||
|
|
66b97be9d2 | ||
|
|
57909ebc61 | ||
|
|
d1090670a8 | ||
|
|
f4bcad1b06 | ||
|
|
6656708c8e | ||
|
|
bfe3fb2ed0 | ||
|
|
ec21521281 | ||
|
|
e71e671d7d | ||
|
|
df6b0fae21 | ||
|
|
69723a8bf4 | ||
|
|
76d907987a | ||
|
|
241caa6132 | ||
|
|
13b93f6d9d | ||
|
|
77555366f4 | ||
|
|
f3e8132bfc | ||
|
|
7510385cf4 | ||
|
|
e05b72615e | ||
|
|
b3f69d4088 | ||
|
|
8796c8fe06 | ||
|
|
b9a6a98fee | ||
|
|
acc9145d52 | ||
|
|
f9c8f53474 | ||
|
|
004e96517f | ||
|
|
957265b1c8 | ||
|
|
8807e282f4 | ||
|
|
c394974dc8 | ||
|
|
28386a4234 | ||
|
|
6f16c47d4f | ||
|
|
25062e37f4 | ||
|
|
2b1f8e4335 | ||
|
|
02690b6796 | ||
|
|
3897281015 | ||
|
|
8d0e5b93ed | ||
|
|
ef146032df | ||
|
|
1dc025fb36 | ||
|
|
30c0c84b93 | ||
|
|
4bd139f93b | ||
|
|
f59787add0 | ||
|
|
20c304a2db | ||
|
|
53526d9c80 | ||
|
|
68b4bc66ff | ||
|
|
805149daea | ||
|
|
b2529207e1 | ||
|
|
d2fe9b0590 | ||
|
|
eff4f82ad7 | ||
|
|
57327332c9 | ||
|
|
a2144ccb61 | ||
|
|
aa478fc510 | ||
|
|
659a0bc0fd | ||
|
|
f1ef28e544 | ||
|
|
fef680fb99 | ||
|
|
91a45a621a | ||
|
|
fe9512ca9c | ||
|
|
ff318599f5 | ||
|
|
d1ba90408d | ||
|
|
536f8d9cb9 | ||
|
|
6a9e871b08 | ||
|
|
d163d145d0 | ||
|
|
fdf0506976 | ||
|
|
c9d8fc3072 | ||
|
|
0bcf67a5f5 | ||
|
|
3e48753329 | ||
|
|
96441dbb33 | ||
|
|
6a0b63b185 | ||
|
|
9dea43fd34 | ||
|
|
465980dd8c | ||
|
|
03d04fd8d1 | ||
|
|
7d18c9b160 | ||
|
|
c097f98959 | ||
|
|
213d4ad928 | ||
|
|
710b3b00f5 | ||
|
|
d041d03fbf | ||
|
|
24b90e9888 | ||
|
|
4b83e798ac | ||
|
|
468f93f3df | ||
|
|
78d9dd5acb | ||
|
|
6ef7d4653d | ||
|
|
645eb637f2 | ||
|
|
268fdce5ac | ||
|
|
f9f3a05a43 | ||
|
|
8a92919b2e | ||
|
|
dbcd675300 | ||
|
|
4628af0e43 | ||
|
|
b1fedf5904 | ||
|
|
45ec03e615 | ||
|
|
570d36b695 | ||
|
|
b36697054e | ||
|
|
17149fe853 | ||
|
|
15c837f745 | ||
|
|
3f43ff05d0 | ||
|
|
d4dee21383 | ||
|
|
79da904767 | ||
|
|
0821797044 | ||
|
|
ad7ff62b56 | ||
|
|
f148d7d0d0 | ||
|
|
e19e3865e7 | ||
|
|
35f062d4e0 | ||
|
|
01328ba33c | ||
|
|
7caa138184 | ||
|
|
1b8c3f3081 | ||
|
|
3e4f36fb6f | ||
|
|
aedcb973f5 | ||
|
|
26dc720929 | ||
|
|
b5cc1c06df | ||
|
|
786c790307 | ||
|
|
3e8660bb61 | ||
|
|
997aed4ab3 | ||
|
|
f64ea058b9 | ||
|
|
96485ba252 | ||
|
|
a9bdcbcdd4 | ||
|
|
1d7f1cccba | ||
|
|
b002477c0d | ||
|
|
27f1447469 | ||
|
|
ff153cdd81 | ||
|
|
fe16329443 | ||
|
|
744e1cf2be | ||
|
|
ab2cf8c8c7 | ||
|
|
fbd001b797 | ||
|
|
4610be6f15 | ||
|
|
d5e34b649f | ||
|
|
7b49a94edd | ||
|
|
8dfcda6c5e | ||
|
|
756d97a9cb | ||
|
|
c0887dbeb9 | ||
|
|
b8f0ff217f | ||
|
|
cc805781e3 | ||
|
|
9531eca7a7 | ||
|
|
d7a1d5bbd2 | ||
|
|
c214168dcd | ||
|
|
dd085be5c7 | ||
|
|
bd7fbfff37 | ||
|
|
52a25fc720 | ||
|
|
384153d914 | ||
|
|
511ac5068b | ||
|
|
67e490ab53 | ||
|
|
b84cde1633 | ||
|
|
3350f7bd56 | ||
|
|
7510419836 | ||
|
|
7da3ef4e0c | ||
|
|
4c32171fbb | ||
|
|
470f178cde | ||
|
|
f9633505a2 | ||
|
|
de102d9898 | ||
|
|
9d503b12e6 | ||
|
|
4dc3bc1532 | ||
|
|
50da8cfbc6 | ||
|
|
bccfc127f3 | ||
|
|
2332d8756d | ||
|
|
a4abaed30a | ||
|
|
d47e18df1f | ||
|
|
c2d094da35 | ||
|
|
6e54b10239 | ||
|
|
e6c44ec52a | ||
|
|
39b8ecf785 | ||
|
|
d87f871334 | ||
|
|
07cea56e2b | ||
|
|
dbf3226f97 | ||
|
|
4b926edf24 | ||
|
|
ce0fd42995 | ||
|
|
bdf91443e0 | ||
|
|
68f4237e15 | ||
|
|
81fc747c03 | ||
|
|
3a25ed13b9 | ||
|
|
21c5eb6786 | ||
|
|
e85203e429 | ||
|
|
6b2d10d589 | ||
|
|
3d03de193e | ||
|
|
a56de72a6b | ||
|
|
0849bbbba6 | ||
|
|
0cd41d2036 | ||
|
|
a93bcd6ab6 | ||
|
|
77b72e160b | ||
|
|
b32c37f4af | ||
|
|
d0beea5cb3 | ||
|
|
30df6887c5 | ||
|
|
bc7823dda1 | ||
|
|
cbb195a313 | ||
|
|
097dc06c65 | ||
|
|
b06fbd25a0 | ||
|
|
89bbf274e5 | ||
|
|
d854177e5a | ||
|
|
aa9f82f2d4 | ||
|
|
2b45264628 | ||
|
|
4da299f431 | ||
|
|
9f9aa447a6 | ||
|
|
6a9f1597cb | ||
|
|
cddc48b851 | ||
|
|
84ab6d300c | ||
|
|
3c5026c2fb | ||
|
|
c3c8959d2e | ||
|
|
2c459d2636 | ||
|
|
a33c481dd5 | ||
|
|
d6b2f93e54 | ||
|
|
802e945829 | ||
|
|
a4a885f33a | ||
|
|
3380cebb28 | ||
|
|
21ed415f4f | ||
|
|
51dd89d36a | ||
|
|
bad96f231c | ||
|
|
184d72c444 | ||
|
|
6ca773050f | ||
|
|
648e8aaae0 | ||
|
|
ad9868b575 | ||
|
|
d4d10998f8 | ||
|
|
ba22c31deb | ||
|
|
f6be133a78 | ||
|
|
3768164f1f | ||
|
|
8465e7539f | ||
|
|
542eca5867 | ||
|
|
27a67c5f4a | ||
|
|
e00c40f2d5 | ||
|
|
b664e231dc | ||
|
|
92d6977f9e | ||
|
|
4059700468 | ||
|
|
196bdd83ba | ||
|
|
fbd52bc14e | ||
|
|
8013c988d0 | ||
|
|
460d6fcf12 | ||
|
|
92353c4853 | ||
|
|
8ca2a89f0f | ||
|
|
f54c2367f3 | ||
|
|
27452085e9 | ||
|
|
2bff7d9567 | ||
|
|
c3749f62fe | ||
|
|
b82c04a16a | ||
|
|
0ec099cdf5 | ||
|
|
d3f49094d8 | ||
|
|
4e2ad3bc62 | ||
|
|
05c287fcf7 | ||
|
|
c11cef4119 | ||
|
|
d6126cc5ce | ||
|
|
d88c925a68 | ||
|
|
372a572400 | ||
|
|
623ee8fbb1 | ||
|
|
729e0fc5ca | ||
|
|
d052cde6ca | ||
|
|
b52bbab903 | ||
|
|
9d42135ebf | ||
|
|
9ce5aa2189 | ||
|
|
e277292194 | ||
|
|
649961c831 | ||
|
|
e00cde2fbd | ||
|
|
25b89d191b | ||
|
|
0a60f77bfc | ||
|
|
66a80e439f | ||
|
|
d8cc25a54f | ||
|
|
39b9cfb348 | ||
|
|
c6087574be | ||
|
|
41c63cbfe9 | ||
|
|
f1afc81e7d | ||
|
|
018f8a19bb | ||
|
|
cccd007ba6 | ||
|
|
6d5b8baadf | ||
|
|
07e36415e4 | ||
|
|
8dc480826b | ||
|
|
533a5b87ec | ||
|
|
7bd83557c1 | ||
|
|
ef290e79b1 | ||
|
|
713b3f4f6d | ||
|
|
8fd9614f69 | ||
|
|
6682a98770 | ||
|
|
392b405978 | ||
|
|
be61850d18 | ||
|
|
f4208a2212 | ||
|
|
0b7278a2a8 | ||
|
|
bee078fe18 |
@@ -42,32 +42,116 @@ jobs:
|
||||
app-build:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
|
||||
resource_class: large
|
||||
working_directory: ~/repo/client
|
||||
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
|
||||
- restore_cache:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
- save_cache:
|
||||
name: Save Yarn Package Cache
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
command: npm i
|
||||
|
||||
- run: yarn run build
|
||||
- run: npm run build
|
||||
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://imex-online-production/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify
|
||||
|
||||
rome-api-deploy:
|
||||
docker:
|
||||
- image: "cimg/base:stable"
|
||||
steps:
|
||||
- checkout
|
||||
- eb/setup
|
||||
- run:
|
||||
command: |
|
||||
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
|
||||
- jira/notify
|
||||
|
||||
rome-hasura-migrate:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
parameters:
|
||||
secret:
|
||||
type: string
|
||||
default: $HASURA_PROD_SECRET
|
||||
working_directory: ~/repo/hasura
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
|
||||
rome-app-build:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
resource_class: large
|
||||
working_directory: ~/repo/client
|
||||
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: npm i
|
||||
|
||||
- run: npm run build
|
||||
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://rome-online-production/"
|
||||
- jira/notify
|
||||
|
||||
test-rome-hasura-migrate:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
parameters:
|
||||
secret:
|
||||
type: string
|
||||
default: $HASURA_ROME_TEST_SECRET
|
||||
working_directory: ~/repo/hasura
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
echo ${HASURA_TEST_SECRET}
|
||||
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
|
||||
test-rome-app-build:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
resource_class: large
|
||||
working_directory: ~/repo/client
|
||||
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: npm i
|
||||
|
||||
- run: npm run build:test
|
||||
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://rome-online-test/"
|
||||
- jira/notify
|
||||
|
||||
app-beta-build:
|
||||
docker:
|
||||
@@ -87,7 +171,27 @@ jobs:
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://imex-online-beta/"
|
||||
- jira/notify
|
||||
- jira/notify
|
||||
|
||||
rome-app-beta-build:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
resource_class: large
|
||||
working_directory: ~/repo/client
|
||||
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: npm i
|
||||
|
||||
- run: npm run build
|
||||
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://rome-online-production-beta/"
|
||||
- jira/notify
|
||||
|
||||
test-hasura-migrate:
|
||||
docker:
|
||||
@@ -112,31 +216,22 @@ jobs:
|
||||
test-app-build:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
|
||||
resource_class: large
|
||||
working_directory: ~/repo/client
|
||||
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
|
||||
- restore_cache:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
- save_cache:
|
||||
name: Save Yarn Package Cache
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
command: npm i
|
||||
|
||||
- run: yarn run build:test
|
||||
- run: npm run build:test
|
||||
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://imex-online-test/"
|
||||
arguments: "--exclude '*.map'"
|
||||
- jira/notify
|
||||
|
||||
test-app-beta-build:
|
||||
@@ -160,6 +255,28 @@ jobs:
|
||||
to: "s3://imex-online-test-beta/"
|
||||
- jira/notify
|
||||
|
||||
rome-test-app-beta-build:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
resource_class: large
|
||||
working_directory: ~/repo/client
|
||||
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: npm i
|
||||
|
||||
- run: npm run build:test
|
||||
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://rome-online-test-beta/"
|
||||
- jira/notify
|
||||
|
||||
|
||||
admin-app-build:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
@@ -205,15 +322,36 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only: master-beta
|
||||
- rome-app-beta-build:
|
||||
filters:
|
||||
branches:
|
||||
only: rome/master-beta
|
||||
- hasura-migrate:
|
||||
secret: ${HASURA_PROD_SECRET}
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- rome-api-deploy:
|
||||
filters:
|
||||
branches:
|
||||
only: rome/master
|
||||
- rome-app-build:
|
||||
filters:
|
||||
branches:
|
||||
only: rome/master
|
||||
- rome-hasura-migrate:
|
||||
secret: ${HASURA_PROD_SECRET}
|
||||
filters:
|
||||
branches:
|
||||
only: rome/master
|
||||
- test-app-build:
|
||||
filters:
|
||||
branches:
|
||||
only: test
|
||||
- rome-test-app-beta-build:
|
||||
filters:
|
||||
branches:
|
||||
only: rome/test-beta
|
||||
- test-app-beta-build:
|
||||
filters:
|
||||
branches:
|
||||
@@ -223,7 +361,16 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only: test
|
||||
- test-rome-app-build:
|
||||
filters:
|
||||
branches:
|
||||
only: rome/test
|
||||
- test-rome-hasura-migrate:
|
||||
secret: ${HASURA_ROME_TEST_SECRET}
|
||||
filters:
|
||||
branches:
|
||||
only: rome/test
|
||||
#- admin-app-build:
|
||||
#filters:
|
||||
#branches:
|
||||
#only: master
|
||||
#only: master
|
||||
|
||||
16
.prettierrc.js
Normal file
16
.prettierrc.js
Normal file
@@ -0,0 +1,16 @@
|
||||
exports.default = {
|
||||
printWidth: 120,
|
||||
useTabs: false,
|
||||
tabWidth: 2,
|
||||
trailingComma: "es5",
|
||||
semi: true,
|
||||
singleQuote: false,
|
||||
bracketSpacing: true,
|
||||
arrowParens: "always",
|
||||
jsxSingleQuote: false,
|
||||
bracketSameLine: false,
|
||||
endOfLine: "lf",
|
||||
importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
|
||||
importOrderSeparation: true,
|
||||
importOrderSortSpecifiers: true,
|
||||
};
|
||||
184
_reference/reportFiltersAndSorters.md
Normal file
184
_reference/reportFiltersAndSorters.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Filters and Sorters
|
||||
|
||||
This documentation details the schema required for `.filters` files on the report server. It is used to dynamically
|
||||
modify the graphQL query and provide the user more power over their reports.
|
||||
|
||||
For filters and sorters, valid types include (`type` key in the schema):
|
||||
- string (default)
|
||||
- number
|
||||
- bool or boolean
|
||||
- date
|
||||
|
||||
## Special Notes
|
||||
- When passing the data to the template server, the property filters and sorters is added to the data object and will reflect the filters and sorters the user has selected
|
||||
|
||||
## High level Schema Overview
|
||||
|
||||
```javascript
|
||||
const schema = {
|
||||
"filters": [
|
||||
{
|
||||
"name": "jobs.joblines.mod_lb_hrs", // Name and path of the field in the graphQL query
|
||||
"translation": "jobs.joblines.mod_lb_hrs_1", // Translation key for the label used in the GUI
|
||||
"label": "mod_lb_hrs_1", // Label used in the case the GUI does not contain a translation
|
||||
"type": "number" // Type of field, can be number or string currently
|
||||
},
|
||||
// ... more filters
|
||||
],
|
||||
"sorters": [
|
||||
{
|
||||
"name": "jobs.joblines.mod_lb_hrs", // Name and path of the field in the graphQL query
|
||||
"translation": "jobs.joblines.mod_lb_hrs_1", // Translation key for the label used in the GUI
|
||||
"label": "mod_lb_hrs_1", // Label used in the case the GUI does not contain a translation
|
||||
"type": "number" // Type of field, can be number or string currently
|
||||
},
|
||||
// ... more sorters
|
||||
],
|
||||
"dates": {
|
||||
// This is not yet implemented and will be added in a future release
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Filters
|
||||
|
||||
Filters effect the where clause of the graphQL query. They are used to filter the data returned from the server.
|
||||
A note on special notation used in the `name` field.
|
||||
|
||||
## Reflection
|
||||
|
||||
Filters can make use of reflection to pre-fill select boxes, the following is an example of that in the filters file.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "jobs.status",
|
||||
"translation": "jobs.fields.status",
|
||||
"label": "Status",
|
||||
"type": "string",
|
||||
"reflector": {
|
||||
"type": "internal",
|
||||
"name": "special.job_statuses"
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
in this example, a reflector with the type 'internal' (all types at the moment require this, and it is used for future functionality), with a name of `special.job_statuses`
|
||||
|
||||
The following cases are available
|
||||
|
||||
- `special.job_statuses` - This will reflect the statuses of the jobs table `bodyshop.md_ro_statuses.statuses'`
|
||||
- `special.cost_centers` - This will reflect the cost centers `bodyshop.md_responsibility_centers.costs`
|
||||
- `special.categories` - This will reflect the categories `bodyshop.md_categories`
|
||||
- `special.insurance_companies` - This will reflect the insurance companies `bodyshop.md_ins_cos`'
|
||||
- `special.employee_teams` - This will reflect the employee teams `bodyshop.employee_teams`
|
||||
- `special.employees` - This will reflect the employees `bodyshop.employees`
|
||||
- `special.first_names` - This will reflect the first names `bodyshop.employees`
|
||||
- `special.last_names` - This will reflect the last names `bodyshop.employees`
|
||||
- `special.referral_sources` - This will reflect the referral sources `bodyshop.md_referral_sources
|
||||
- `special.class`- This will reflect the class `bodyshop.md_classes`
|
||||
-
|
||||
### Path without brackets, multi level
|
||||
|
||||
`"name": "jobs.joblines.mod_lb_hrs",`
|
||||
This will produce a where clause at the `joblines` level of the graphQL query,
|
||||
|
||||
```graphql
|
||||
query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!) {
|
||||
jobs(
|
||||
where: {date_invoiced: {_is_null: true}, date_open: {_gte: $starttz, _lte: $endtz}, ro_number: {_is_null: false}, voided: {_eq: false}}
|
||||
) {
|
||||
joblines(
|
||||
order_by: {line_no: asc}
|
||||
where: {removed: {_eq: false}, mod_lb_hrs: {_lt: 3}}
|
||||
) {
|
||||
line_no
|
||||
mod_lbr_ty
|
||||
mod_lb_hrs
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
}
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
plate_no
|
||||
ro_number
|
||||
status
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
v_color
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Path with brackets,top level
|
||||
|
||||
`"name": "[jobs].joblines.mod_lb_hrs",`
|
||||
This will produce a where clause at the `jobs` level of the graphQL query.
|
||||
|
||||
```graphql
|
||||
query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!) {
|
||||
jobs(
|
||||
where: {date_invoiced: {_is_null: true}, date_open: {_gte: $starttz, _lte: $endtz}, ro_number: {_is_null: false}, voided: {_eq: false}, joblines: {mod_lb_hrs: {_gt: 4}}}
|
||||
) {
|
||||
joblines(
|
||||
order_by: {line_no: asc}
|
||||
where: {removed: {_eq: false}}
|
||||
) {
|
||||
line_no
|
||||
mod_lbr_ty
|
||||
mod_lb_hrs
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
}
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
plate_no
|
||||
ro_number
|
||||
status
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
v_color
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Known Caveats
|
||||
|
||||
- Will only support two level of nesting in the graphQL query `jobs.joblines.mod_lb_hrs` vs `[jobs].joblines.mod_lb_hrs`
|
||||
is fine, but `jobs.[joblines.].some_table.mod_lb_hrs` is not.
|
||||
- The type object must be 'string' or 'number' or 'bool' or 'boolean' or 'date' and is case-sensitive.
|
||||
- The `translation` key is used to look up the label in the GUI, if it is not found, the `label` key is used.
|
||||
- Do not add the ability to filter things that are already filtered as part of the original query, this would be
|
||||
redundant and could cause issues.
|
||||
- Do not add the ability to filter on things like FK constraints, must like the above example.
|
||||
|
||||
## Sorters
|
||||
|
||||
- Sorters follow the same schema as filters, however, they do not do square bracket wrapping to indicate level hoisting,
|
||||
a filter added on `job.md_status` would be added at the top level, and a filter added on `jobs.joblines.mod_lb_hrs`
|
||||
would be added at the `joblines` level.
|
||||
- Most of the reports currently do sorting on a template level, this will need to change to actually see the results
|
||||
using the sorters.
|
||||
|
||||
### Default Sorters
|
||||
|
||||
- A sorter can be given a default object containing a `order` and `direction` key value. This will be used to sort the report if the user does not select any of the sorters themselves.
|
||||
- The `order` key is the order in which the sorters are applied, and the `direction` key is the direction of the sort, either `asc` or `desc`.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "jobs.joblines.mod_lb_hrs",
|
||||
"translation": "jobs.joblines.mod_lb_hrs_1",
|
||||
"label": "mod_lb_hrs_1",
|
||||
"type": "number",
|
||||
"default": {
|
||||
"order": 1,
|
||||
"direction": "asc"
|
||||
}
|
||||
}
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,14 @@
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
REACT_APP_GA_CODE=231099835
|
||||
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||
REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
||||
REACT_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
REACT_APP_COUNTRY=USA
|
||||
@@ -1,14 +1,13 @@
|
||||
GENERATE_SOURCEMAP=false
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
|
||||
REACT_APP_GA_CODE=231103507
|
||||
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
|
||||
REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
|
||||
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
|
||||
REACT_APP_CLOUDINARY_API_KEY=473322739956866
|
||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
|
||||
REACT_APP_REPORTS_SERVER_URL=https://reports.imex.online
|
||||
REACT_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
|
||||
REACT_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
||||
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||
@@ -1,14 +1,14 @@
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql
|
||||
REACT_APP_GA_CODE=231099835
|
||||
REACT_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql
|
||||
REACT_APP_GA_CODE=231103507
|
||||
REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
|
||||
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
|
||||
REACT_APP_CLOUDINARY_API_KEY=473322739956866
|
||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
REACT_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
|
||||
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||
REACT_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/
|
||||
REACT_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
||||
REACT_APP_IS_TEST=true
|
||||
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
3
client/.gitignore
vendored
Normal file
3
client/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
@@ -1 +0,0 @@
|
||||
legacy-peer-deps=true
|
||||
@@ -1,69 +1,73 @@
|
||||
// craco.config.js
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const CracoLessPlugin = require("craco-less");
|
||||
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
||||
const {convertLegacyToken} = require('@ant-design/compatible/lib');
|
||||
const {theme} = require('antd/lib');
|
||||
|
||||
const {defaultAlgorithm, defaultSeed} = theme;
|
||||
|
||||
const mapToken = defaultAlgorithm(defaultSeed);
|
||||
const v4Token = convertLegacyToken(mapToken);
|
||||
|
||||
// TODO, At the moment we are using less in the Dashboard. Once we remove this we can remove the less processor entirely.
|
||||
//const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
plugin: SentryWebpackPlugin,
|
||||
options: {
|
||||
// sentry-cli configuration
|
||||
authToken:
|
||||
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
||||
org: "snapt-software",
|
||||
project: "imexonline",
|
||||
release: process.env.REACT_APP_GIT_SHA,
|
||||
plugins: [
|
||||
// {
|
||||
// plugin: SentryWebpackPlugin,
|
||||
// options: {
|
||||
// // sentry-cli configuration
|
||||
// authToken:
|
||||
// "6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
||||
// org: "snapt-software",
|
||||
// project: "imexonline",
|
||||
// release: process.env.REACT_APP_GIT_SHA,
|
||||
|
||||
// webpack-specific configuration
|
||||
include: ".",
|
||||
ignore: ["node_modules", "webpack.config.js"],
|
||||
// // webpack-specific configuration
|
||||
// include: ".",
|
||||
// ignore: ["node_modules", "webpack.config.js"],
|
||||
// },
|
||||
// },
|
||||
{
|
||||
plugin: CracoLessPlugin,
|
||||
options: {
|
||||
lessLoaderOptions: {
|
||||
lessOptions: {
|
||||
modifyVars: {
|
||||
...(process.env.NODE_ENV === "development"
|
||||
? { "@primary-color": "#B22234" }
|
||||
: {
|
||||
//"@primary-color": "#1DA57A"
|
||||
}),
|
||||
// "@primary-color": " #1890ff", // primary color for all components
|
||||
// "@link-color": "#1890ff", // link color
|
||||
// "@success-color": "#52c41a", // success state color
|
||||
// "@warning-color": "#faad14", // warning state color
|
||||
// "@error-color": "#f5222d", // error state color
|
||||
// "@font-size-base": "14px", // major text font size
|
||||
// " @heading-color": "rgba(0, 0, 0, 0.85)", // heading text color
|
||||
// "@text-color": "rgba(0, 0, 0, 0.65)", // major text color
|
||||
// "@text-color-secondary": "rgba(0, 0, 0, 0.45)", // secondary text color
|
||||
// "@disabled-color": "rgba(0, 0, 0, 0.25)", // disable state color
|
||||
// "@border-radius-base": "2px", // major border radius
|
||||
// "@border-color-base": "#d9d9d9", // major border color
|
||||
// "@box-shadow-base":
|
||||
// "0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),0 9px 28px 8px rgba(0, 0, 0, 0.05); // major shadow for layers }",
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
plugin: CracoLessPlugin,
|
||||
options: {
|
||||
lessLoaderOptions: {
|
||||
lessOptions: {
|
||||
modifyVars: {...v4Token},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
webpack: {
|
||||
configure: (webpackConfig) => {
|
||||
return {
|
||||
...webpackConfig,
|
||||
// Required for Dev Server
|
||||
devServer: {
|
||||
...webpackConfig.devServer,
|
||||
allowedHosts: 'all',
|
||||
},
|
||||
optimization: {
|
||||
...webpackConfig.optimization,
|
||||
// Workaround for CircleCI bug caused by the number of CPUs shown
|
||||
// https://github.com/facebook/create-react-app/issues/8320
|
||||
minimizer: webpackConfig.optimization.minimizer.map((item) => {
|
||||
if (item instanceof TerserPlugin) {
|
||||
item.options.parallel = 2;
|
||||
}
|
||||
|
||||
return item;
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
devtool: "source-map",
|
||||
],
|
||||
webpack: {
|
||||
configure: (webpackConfig) => ({
|
||||
...webpackConfig,
|
||||
optimization: {
|
||||
...webpackConfig.optimization,
|
||||
// Workaround for CircleCI bug caused by the number of CPUs shown
|
||||
// https://github.com/facebook/create-react-app/issues/8320
|
||||
minimizer: webpackConfig.optimization.minimizer.map((item) => {
|
||||
if (item instanceof TerserPlugin) {
|
||||
item.options.parallel = 2;
|
||||
}
|
||||
|
||||
return item;
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
devtool: "source-map",
|
||||
};
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
experimentalStudio: true,
|
||||
env: {
|
||||
FIREBASE_USERNAME: 'cypress@imex.test',
|
||||
FIREBASE_PASSWORD: 'cypress',
|
||||
},
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents(on, config) {
|
||||
return require('./cypress/plugins/index.js')(on, config)
|
||||
},
|
||||
baseUrl: 'http://localhost:3000',
|
||||
},
|
||||
})
|
||||
8
client/cypress.json
Normal file
8
client/cypress.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:3000",
|
||||
"experimentalStudio": true,
|
||||
"env": {
|
||||
"FIREBASE_USERNAME": "cypress@imex.test",
|
||||
"FIREBASE_PASSWORD": "cypress"
|
||||
}
|
||||
}
|
||||
@@ -8872,13 +8872,13 @@
|
||||
│ ├─ email: luis@luisrudge.net
|
||||
│ ├─ path: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-flexbugs-fixes
|
||||
│ └─ licenseFile: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-flexbugs-fixes/LICENSE
|
||||
├─ postcss-focus-open@4.0.0
|
||||
├─ postcss-focus-visible@4.0.0
|
||||
│ ├─ licenses: CC0-1.0
|
||||
│ ├─ repository: https://github.com/jonathantneal/postcss-focus-open
|
||||
│ ├─ repository: https://github.com/jonathantneal/postcss-focus-visible
|
||||
│ ├─ publisher: Jonathan Neal
|
||||
│ ├─ email: jonathantneal@hotmail.com
|
||||
│ ├─ path: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-open
|
||||
│ └─ licenseFile: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-open/LICENSE.md
|
||||
│ ├─ path: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-visible
|
||||
│ └─ licenseFile: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-visible/LICENSE.md
|
||||
├─ postcss-focus-within@3.0.0
|
||||
│ ├─ licenses: CC0-1.0
|
||||
│ ├─ repository: https://github.com/jonathantneal/postcss-focus-within
|
||||
|
||||
16875
client/package-lock.json
generated
16875
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,97 +4,100 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@ant-design/compatible": "^5.1.2",
|
||||
"@ant-design/pro-layout": "^7.17.16",
|
||||
"@apollo/client": "^3.8.10",
|
||||
"@apollo/client": "^3.7.9",
|
||||
"@asseinfo/react-kanban": "^2.2.0",
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@fingerprintjs/fingerprintjs": "^4.2.1",
|
||||
"@craco/craco": "^7.0.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@reduxjs/toolkit": "^2.0.1",
|
||||
"@sentry/react": "^7.93.0",
|
||||
"@sentry/tracing": "^7.93.0",
|
||||
"@splitsoftware/splitio-react": "^1.11.0",
|
||||
"@tanem/react-nprogress": "^5.0.51",
|
||||
"antd": "^5.12.8",
|
||||
"@sentry/cli": "^2.27.0",
|
||||
"@sentry/react": "^7.99.0",
|
||||
"@sentry/tracing": "^7.40.0",
|
||||
"@splitsoftware/splitio-react": "^1.8.1",
|
||||
"@tanem/react-nprogress": "^5.0.8",
|
||||
"antd": "^4.24.8",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"axios": "^1.6.5",
|
||||
"craco-less": "^3.0.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"dayjs-business-days2": "^1.2.2",
|
||||
"apollo-link-sentry": "^3.3.0",
|
||||
"axios": "^1.3.4",
|
||||
"craco-less": "^2.0.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"enquire-js": "^0.2.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^10.7.2",
|
||||
"firebase": "^9.17.1",
|
||||
"graphql": "^16.6.0",
|
||||
"i18next": "^23.7.16",
|
||||
"i18next-browser-languagedetector": "^7.0.2",
|
||||
"jsoneditor": "^10.0.0",
|
||||
"i18next": "^22.4.10",
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
"jsoneditor": "^9.9.0",
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.10.53",
|
||||
"logrocket": "^7.0.0",
|
||||
"markerjs2": "^2.31.4",
|
||||
"libphonenumber-js": "^1.10.21",
|
||||
"logrocket": "^3.0.1",
|
||||
"markerjs2": "^2.28.1",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"moment-timezone": "^0.5.41",
|
||||
"normalize-url": "^8.0.0",
|
||||
"phone": "^3.1.42",
|
||||
"phone": "^3.1.35",
|
||||
"preval.macro": "^5.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^8.1.0",
|
||||
"query-string": "^7.1.3",
|
||||
"rc-queue-anim": "^2.0.0",
|
||||
"rc-scroll-anim": "^2.7.6",
|
||||
"react": "^18.2.0",
|
||||
"react-big-calendar": "^1.8.6",
|
||||
"react": "^17.0.2",
|
||||
"react-big-calendar": "^1.6.8",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^7.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-drag-listview": "^0.2.1",
|
||||
"react-grid-gallery": "^1.0.0",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-i18next": "^14.0.0",
|
||||
"react-icons": "^5.0.1",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-i18next": "^12.2.0",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-intersection-observer": "^9.5.3",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-number-format": "^5.1.4",
|
||||
"react-redux": "^9.1.0",
|
||||
"react-resizable": "^3.0.5",
|
||||
"react-router-dom": "^6.21.3",
|
||||
"react-intersection-observer": "^9.4.3",
|
||||
"react-number-format": "^5.1.3",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-resizable": "^3.0.4",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-sublime-video": "^0.2.5",
|
||||
"react-virtualized": "^9.22.5",
|
||||
"recharts": "^2.10.4",
|
||||
"redux": "^5.0.1",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"recharts": "^2.4.3",
|
||||
"redux": "^4.2.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.3.0",
|
||||
"redux-saga": "^1.2.2",
|
||||
"redux-state-sync": "^3.1.4",
|
||||
"reselect": "^5.1.0",
|
||||
"sass": "^1.70.0",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"styled-components": "^6.1.8",
|
||||
"reselect": "^4.1.7",
|
||||
"sass": "^1.58.3",
|
||||
"socket.io-client": "^4.6.1",
|
||||
"styled-components": "^5.3.6",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"web-vitals": "^3.5.1",
|
||||
"workbox-core": "^7.0.0",
|
||||
"workbox-expiration": "^7.0.0",
|
||||
"workbox-navigation-preload": "^7.0.0",
|
||||
"workbox-precaching": "^7.0.0",
|
||||
"workbox-routing": "^7.0.0",
|
||||
"workbox-strategies": "^7.0.0",
|
||||
"web-vitals": "^2.1.4",
|
||||
"workbox-background-sync": "^6.5.3",
|
||||
"workbox-broadcast-update": "^6.5.3",
|
||||
"workbox-cacheable-response": "^6.5.3",
|
||||
"workbox-core": "^6.5.3",
|
||||
"workbox-expiration": "^6.5.3",
|
||||
"workbox-google-analytics": "^6.5.3",
|
||||
"workbox-navigation-preload": "^6.5.3",
|
||||
"workbox-precaching": "^6.5.3",
|
||||
"workbox-range-requests": "^6.5.3",
|
||||
"workbox-routing": "^6.5.3",
|
||||
"workbox-strategies": "^6.5.3",
|
||||
"workbox-streams": "^6.5.3",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"start": "craco start",
|
||||
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
||||
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build && npm run sentry:sourcemaps",
|
||||
"build:test": "env-cmd -f .env.test 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",
|
||||
"eulaize": "node src/utils/eulaize.js",
|
||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular ."
|
||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
||||
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@@ -119,13 +122,12 @@
|
||||
"react-error-overlay": "6.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@sentry/webpack-plugin": "^2.10.2",
|
||||
"@testing-library/cypress": "^10.0.1",
|
||||
"cypress": "^13.6.3",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"@sentry/webpack-plugin": "^1.20.0",
|
||||
"@testing-library/cypress": "^8.0.3",
|
||||
"cypress": "^10.3.1",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"react-error-overlay": "6.0.11",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.3"
|
||||
"source-map-explorer": "^2.5.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ This package contains the following license and notice below:
|
||||
# @firebase/logger
|
||||
|
||||
This package serves as the base of all logging in the JS SDK. Any logging that
|
||||
is intended to be open to Firebase end developers should go through this
|
||||
is intended to be visible to Firebase end developers should go through this
|
||||
module.
|
||||
|
||||
## Basic Usage
|
||||
@@ -9375,7 +9375,7 @@ parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently open
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
|
||||
@@ -1029,7 +1029,7 @@ The following NPM packages may be included in this product:
|
||||
- postcss-dir-pseudo-class@5.0.0
|
||||
- postcss-double-position-gradients@1.0.0
|
||||
- postcss-env-function@2.0.2
|
||||
- postcss-focus-open@4.0.0
|
||||
- postcss-focus-visible@4.0.0
|
||||
- postcss-focus-within@3.0.0
|
||||
- postcss-gap-properties@2.0.0
|
||||
- postcss-image-set-function@3.0.1
|
||||
@@ -1699,7 +1699,7 @@ This package contains the following license and notice below:
|
||||
# @firebase/logger
|
||||
|
||||
This package serves as the base of all logging in the JS SDK. Any logging that
|
||||
is intended to be open to Firebase end developers should go through this
|
||||
is intended to be visible to Firebase end developers should go through this
|
||||
module.
|
||||
|
||||
## Basic Usage
|
||||
@@ -24029,7 +24029,7 @@ parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently open
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
|
||||
@@ -28,6 +28,17 @@ switch (this.location.hostname) {
|
||||
// measurementId: "${config.measurementId}",
|
||||
};
|
||||
break;
|
||||
case "romeonline.io":
|
||||
firebaseConfig = {
|
||||
apiKey: "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE",
|
||||
authDomain: "rome-prod-1.firebaseapp.com",
|
||||
projectId: "rome-prod-1",
|
||||
storageBucket: "rome-prod-1.appspot.com",
|
||||
messagingSenderId: "147786367145",
|
||||
appId: "1:147786367145:web:9d4cba68071c3f29a8a9b8",
|
||||
measurementId: "G-G8Z9DRHTZS",
|
||||
};
|
||||
break;
|
||||
case "imex.online":
|
||||
default:
|
||||
firebaseConfig = {
|
||||
|
||||
@@ -2,23 +2,81 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.png" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/ro-favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#002366" />
|
||||
<meta name="description" content="ImEX Online" />
|
||||
<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 type="text/javascript">
|
||||
window.$crisp = [];
|
||||
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
|
||||
(function () {
|
||||
d = document;
|
||||
s = d.createElement("script");
|
||||
s.src = "https://client.crisp.chat/l.js";
|
||||
s.async = 1;
|
||||
d.getElementsByTagName("head")[0].appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
!(function () {
|
||||
"use strict";
|
||||
@@ -77,7 +135,7 @@
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>ImEX Online</title>
|
||||
<title>Rome Online</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "ImEX Online",
|
||||
"name": "ImEX Online",
|
||||
"short_name": "Rome Online",
|
||||
"name": "Rome Online",
|
||||
"description": "The ultimate bodyshop management system.",
|
||||
"icons": [
|
||||
{
|
||||
|
||||
BIN
client/public/ro-favicon.png
Normal file
BIN
client/public/ro-favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 376 B |
@@ -1,50 +1,53 @@
|
||||
import {ApolloProvider} from "@apollo/client";
|
||||
import {SplitFactoryProvider, SplitSdk,} from '@splitsoftware/splitio-react';
|
||||
import {ConfigProvider} from "antd";
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
import { SplitFactory, SplitSdk } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import dayjs from "../utils/day";
|
||||
import 'dayjs/locale/en';
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
import * as Sentry from "@sentry/react";
|
||||
|
||||
import themeProvider from "./themeProvider";
|
||||
moment.locale("en-US");
|
||||
|
||||
dayjs.locale("en");
|
||||
export const factory = SplitSdk({
|
||||
core: {
|
||||
authorizationKey: process.env.REACT_APP_SPLIT_API,
|
||||
key: "anon",
|
||||
},
|
||||
});
|
||||
|
||||
const config = {
|
||||
core: {
|
||||
authorizationKey: process.env.REACT_APP_SPLIT_API,
|
||||
key: "anon",
|
||||
},
|
||||
};
|
||||
export const factory = SplitSdk(config);
|
||||
function AppContainer() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
export default function AppContainer() {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
//componentSize="small"
|
||||
input={{autoComplete: "new-password"}}
|
||||
locale={enLocale}
|
||||
theme={themeProvider}
|
||||
form={{
|
||||
validateMessages: {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
required: t("general.validation.required", {label: "${label}"}),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<GlobalLoadingBar/>
|
||||
<SplitFactoryProvider factory={factory}>
|
||||
<App/>
|
||||
</SplitFactoryProvider>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
);
|
||||
return (
|
||||
<ApolloProvider client={client}>
|
||||
<ConfigProvider
|
||||
//componentSize="small"
|
||||
input={{ autoComplete: "new-password" }}
|
||||
locale={enLocale}
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: "#326ade",
|
||||
colorInfo: "#326ade"
|
||||
},
|
||||
}}
|
||||
form={{
|
||||
validateMessages: {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
required: t("general.validation.required", { label: "${label}" }),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<GlobalLoadingBar />
|
||||
<SplitFactory factory={factory}>
|
||||
<App />
|
||||
</SplitFactory>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sentry.withProfiler(AppContainer);
|
||||
|
||||
@@ -1,155 +1,164 @@
|
||||
import {useSplitClient} from "@splitsoftware/splitio-react";
|
||||
import {Button, Result} from "antd";
|
||||
import { useClient } from "@splitsoftware/splitio-react";
|
||||
import { Button, Result } from "antd";
|
||||
import LogRocket from "logrocket";
|
||||
import React, {lazy, Suspense, useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Route, Routes} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import React, { lazy, Suspense, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Route, Switch } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||
|
||||
//Component Imports
|
||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
||||
import LandingPage from "../pages/landing/landing.page";
|
||||
import TechPageContainer from "../pages/tech/tech.page.container";
|
||||
import {setOnline} from "../redux/application/application.actions";
|
||||
import {selectOnline} from "../redux/application/application.selectors";
|
||||
import {checkUserSession} from "../redux/user/user.actions";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../redux/user/user.selectors";
|
||||
import PrivateRoute from "../components/PrivateRoute";
|
||||
import { setOnline } from "../redux/application/application.actions";
|
||||
import { selectOnline } from "../redux/application/application.selectors";
|
||||
import { checkUserSession } from "../redux/user/user.actions";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../redux/user/user.selectors";
|
||||
import PrivateRoute from "../utils/private-route";
|
||||
import "./App.styles.scss";
|
||||
import handleBeta from "../utils/betaHandler";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
import handleBeta from "../utils/handleBeta";
|
||||
|
||||
const ResetPassword = lazy(() =>
|
||||
import("../pages/reset-password/reset-password.component")
|
||||
import("../pages/reset-password/reset-password.component")
|
||||
);
|
||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
||||
|
||||
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
||||
const MobilePaymentContainer = lazy(() =>
|
||||
import("../pages/mobile-payment/mobile-payment.container")
|
||||
import("../pages/mobile-payment/mobile-payment.container")
|
||||
);
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
online: selectOnline,
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
online: selectOnline,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
checkUserSession: () => dispatch(checkUserSession()),
|
||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||
checkUserSession: () => dispatch(checkUserSession()),
|
||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||
});
|
||||
|
||||
export function App({bodyshop, checkUserSession, currentUser, online, setOnline}) {
|
||||
const client = useSplitClient().client;
|
||||
const [listenersAdded, setListenersAdded] = useState(false)
|
||||
const {t} = useTranslation();
|
||||
export function App({
|
||||
bodyshop,
|
||||
checkUserSession,
|
||||
currentUser,
|
||||
online,
|
||||
setOnline,
|
||||
}) {
|
||||
const client = useClient();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!navigator.onLine) {
|
||||
setOnline(false);
|
||||
}
|
||||
|
||||
checkUserSession();
|
||||
}, [checkUserSession, setOnline]);
|
||||
|
||||
//const b = Grid.useBreakpoint();
|
||||
// console.log("Breakpoints:", b);
|
||||
|
||||
// Associate event listeners, memoize to prevent multiple listeners being added
|
||||
useEffect(() => {
|
||||
const offlineListener = (e) => {
|
||||
setOnline(false);
|
||||
}
|
||||
|
||||
const onlineListener = (e) => {
|
||||
setOnline(true);
|
||||
}
|
||||
|
||||
if (!listenersAdded) {
|
||||
console.log('Added events for offline and online');
|
||||
window.addEventListener("offline", offlineListener);
|
||||
window.addEventListener("online", onlineListener);
|
||||
setListenersAdded(true);
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("offline", offlineListener);
|
||||
window.removeEventListener("online", onlineListener);
|
||||
}
|
||||
}, [setOnline, listenersAdded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser.authorized && bodyshop) {
|
||||
client.setAttribute("imexshopid", bodyshop.imexshopid);
|
||||
|
||||
if (
|
||||
client.getTreatment("LogRocket_Tracking") === "on" ||
|
||||
window.location.hostname === 'beta.imex.online'
|
||||
) {
|
||||
console.log("LR Start");
|
||||
LogRocket.init("gvfvfw/bodyshopapp");
|
||||
}
|
||||
}
|
||||
}, [bodyshop, client, currentUser.authorized]);
|
||||
|
||||
if (currentUser.authorized === null) {
|
||||
return <LoadingSpinner message={t("general.labels.loggingin")}/>;
|
||||
useEffect(() => {
|
||||
if (!navigator.onLine) {
|
||||
setOnline(false);
|
||||
}
|
||||
checkUserSession();
|
||||
}, [checkUserSession, setOnline]);
|
||||
|
||||
handleBeta();
|
||||
//const b = Grid.useBreakpoint();
|
||||
// console.log("Breakpoints:", b);
|
||||
|
||||
if (!online)
|
||||
return (
|
||||
<Result
|
||||
status="warning"
|
||||
title={t("general.labels.nointernet")}
|
||||
subTitle={t("general.labels.nointernet_sub")}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
{t("general.actions.refresh")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!currentUser.eulaIsAccepted) {
|
||||
return <Eula/>
|
||||
window.addEventListener("offline", function (e) {
|
||||
setOnline(false);
|
||||
});
|
||||
|
||||
window.addEventListener("online", function (e) {
|
||||
setOnline(true);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (currentUser.authorized && bodyshop) {
|
||||
client.setAttribute("imexshopid", bodyshop.imexshopid);
|
||||
|
||||
LogRocket.init("rome-online/rome-online");
|
||||
if (client.getTreatment("LogRocket_Tracking") === "on") {
|
||||
LogRocket.init("rome-online/rome-online");
|
||||
}
|
||||
}
|
||||
}, [bodyshop, client, currentUser.authorized]);
|
||||
|
||||
// Any route that is not assigned and matched will default to the Landing Page component
|
||||
if (currentUser.authorized === null) {
|
||||
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
||||
}
|
||||
|
||||
if (!online)
|
||||
return (
|
||||
<Suspense fallback={<LoadingSpinner message="ImEX Online"/>}>
|
||||
<Routes>
|
||||
<Route path="*" element={<ErrorBoundary><LandingPage/></ErrorBoundary>}/>
|
||||
<Route path="/signin" element={<ErrorBoundary><SignInPage/></ErrorBoundary>}/>
|
||||
<Route path="/resetpassword" element={<ErrorBoundary><ResetPassword/></ErrorBoundary>}/>
|
||||
<Route path="/csi/:surveyId" element={<ErrorBoundary><CsiPage/></ErrorBoundary>}/>
|
||||
<Route path="/disclaimer" element={<ErrorBoundary><DisclaimerPage/></ErrorBoundary>}/>
|
||||
<Route path="/mp/:paymentIs" element={<ErrorBoundary><MobilePaymentContainer/></ErrorBoundary>}/>
|
||||
<Route path="/manage/*"
|
||||
element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
|
||||
<Route path="*" element={<ManagePage/>}/>
|
||||
</Route>
|
||||
<Route path="/tech/*"
|
||||
element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
|
||||
<Route path="*" element={<TechPageContainer/>}/>
|
||||
</Route>
|
||||
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized}/>}>
|
||||
<Route path="*" element={<DocumentEditorContainer/>}/>
|
||||
</Route>
|
||||
</Routes>
|
||||
</Suspense>
|
||||
<Result
|
||||
status="warning"
|
||||
title={t("general.labels.nointernet")}
|
||||
subTitle={t("general.labels.nointernet_sub")}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
{t("general.actions.refresh")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
handleBeta();
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Suspense fallback={<LoadingSpinner />}>
|
||||
<ErrorBoundary>
|
||||
<Route exact path="/" component={LandingPage} />
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<Route exact path="/signin" component={SignInPage} />
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<Route exact path="/resetpassword" component={ResetPassword} />
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<Route exact path="/csi/:surveyId" component={CsiPage} />
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<Route exact path="/disclaimer" component={DisclaimerPage} />
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<Route
|
||||
exact
|
||||
path="/mp/:paymentIs"
|
||||
component={MobilePaymentContainer}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute
|
||||
isAuthorized={currentUser.authorized}
|
||||
path="/manage"
|
||||
component={ManagePage}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute
|
||||
isAuthorized={currentUser.authorized}
|
||||
path="/tech"
|
||||
component={TechPageContainer}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute
|
||||
isAuthorized={currentUser.authorized}
|
||||
path="/edit"
|
||||
component={DocumentEditorContainer}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</Suspense>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
//Global Styles.
|
||||
@import "react-big-calendar/lib/sass/styles";
|
||||
|
||||
.ant-menu-item-divider {
|
||||
border-bottom: 1px solid #74695c !important;
|
||||
}
|
||||
|
||||
.imex-table-header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import {defaultsDeep} from "lodash";
|
||||
|
||||
/**
|
||||
* Default theme
|
||||
* @type {{components: {Menu: {itemDividerBorderColor: string}}}}
|
||||
*/
|
||||
const defaultTheme = {
|
||||
components: {
|
||||
Menu: {
|
||||
darkItemHoverBg: '#1677ff',
|
||||
itemHoverBg: '#1677ff',
|
||||
horizontalItemHoverBg: '#1677ff',
|
||||
}
|
||||
},
|
||||
token: {
|
||||
colorPrimary: '#1677ff'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Development theme
|
||||
* @type {{components: {Menu: {itemHoverBg: string, darkItemHoverBg: string, horizontalItemHoverBg: string}}, token: {colorPrimary: string}}}
|
||||
*/
|
||||
const devTheme = {
|
||||
components: {
|
||||
Menu: {
|
||||
darkItemHoverBg: '#a51d1d',
|
||||
itemHoverBg: '#a51d1d',
|
||||
horizontalItemHoverBg: '#a51d1d',
|
||||
}
|
||||
},
|
||||
token: {
|
||||
colorPrimary: '#a51d1d'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Production theme
|
||||
* @type {{components: {Menu: {itemHoverBg: string, darkItemHoverBg: string, horizontalItemHoverBg: string}}, token: {colorPrimary: string}}}
|
||||
*/
|
||||
const prodTheme = {};
|
||||
|
||||
const theme = process.env.NODE_ENV === "development" ? devTheme
|
||||
: prodTheme;
|
||||
|
||||
export default defaultsDeep(theme, defaultTheme);
|
||||
BIN
client/src/assets/RomeOnline.png
Normal file
BIN
client/src/assets/RomeOnline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
client/src/assets/RomeOnlineBlue.png
Normal file
BIN
client/src/assets/RomeOnlineBlue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
client/src/assets/romelogo.png
Normal file
BIN
client/src/assets/romelogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -1,17 +0,0 @@
|
||||
import React, {useEffect} from "react";
|
||||
import {Outlet, useLocation, useNavigate} from "react-router-dom";
|
||||
|
||||
function PrivateRoute({component: Component, isAuthorized, ...rest}) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthorized) {
|
||||
navigate(`/signin?redirect=${location.pathname}`);
|
||||
}
|
||||
}, [isAuthorized, navigate,location]);
|
||||
|
||||
return <Outlet/>;
|
||||
}
|
||||
|
||||
export default PrivateRoute;
|
||||
@@ -2,5 +2,5 @@ import { Alert } from "antd";
|
||||
import React from "react";
|
||||
|
||||
export default function AlertComponent(props) {
|
||||
return <Alert {...props} />;
|
||||
return <Alert {...props} />;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export function AllocationsAssignmentComponent({
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={popContent} open={visibility}>
|
||||
<Popover content={popContent} visible={visibility}>
|
||||
<Button onClick={() => setVisibility(true)}>
|
||||
{t("allocations.actions.assign")}
|
||||
</Button>
|
||||
|
||||
@@ -59,7 +59,7 @@ export default connect(
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={popContent} open={visibility}>
|
||||
<Popover content={popContent} visible={visibility}>
|
||||
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||
{t("allocations.actions.assign")}
|
||||
</Button>
|
||||
|
||||
@@ -3,10 +3,22 @@ import { useMutation } from "@apollo/client";
|
||||
import { Button, notification, Popconfirm } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { DELETE_BILL } from "../../graphql/bills.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
|
||||
export default function BillDeleteButton({ bill, callback }) {
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BillDeleteButton);
|
||||
|
||||
export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [deleteBill] = useMutation(DELETE_BILL);
|
||||
@@ -36,6 +48,10 @@ export default function BillDeleteButton({ bill, callback }) {
|
||||
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({ message: t("bills.successes.deleted") });
|
||||
insertAuditTrail({
|
||||
jobid: jobid,
|
||||
operation: AuditTrailMapping.billdeleted(bill.invoice_number),
|
||||
});
|
||||
|
||||
if (callback && typeof callback === "function") callback(bill.id);
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {useMutation, useQuery} from "@apollo/client";
|
||||
import {Button, Form, Popconfirm, Space} from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Form, PageHeader, Popconfirm, Space } from "antd";
|
||||
import moment from "moment";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -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";
|
||||
@@ -26,142 +26,143 @@ import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-galler
|
||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import BillDetailEditReturn from "./bill-detail-edit-return.component";
|
||||
import {PageHeader} from "@ant-design/pro-layout";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPartsOrderContext: (context) =>
|
||||
dispatch(setModalContext({context: context, modal: "partsOrder"})),
|
||||
insertAuditTrail: ({jobid, operation}) =>
|
||||
dispatch(insertAuditTrail({jobid, operation})),
|
||||
setPartsOrderContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(BillDetailEditcontainer);
|
||||
|
||||
export function BillDetailEditcontainer({setPartsOrderContext, insertAuditTrail, bodyshop,}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
export function BillDetailEditcontainer({
|
||||
setPartsOrderContext,
|
||||
insertAuditTrail,
|
||||
bodyshop,
|
||||
}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
|
||||
const {t} = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [updateLoading, setUpdateLoading] = useState(false);
|
||||
const [update_bill] = useMutation(UPDATE_BILL);
|
||||
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
|
||||
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
|
||||
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [updateLoading, setUpdateLoading] = useState(false);
|
||||
const [update_bill] = useMutation(UPDATE_BILL);
|
||||
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
|
||||
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
|
||||
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
|
||||
|
||||
const {loading, error, data, refetch} = useQuery(QUERY_BILL_BY_PK, {
|
||||
variables: {billid: search.billid},
|
||||
skip: !!!search.billid,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
|
||||
variables: { billid: search.billid },
|
||||
skip: !!!search.billid,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
//It's got a previously deducted bill line!
|
||||
if (
|
||||
data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 ||
|
||||
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
|
||||
0
|
||||
)
|
||||
setVisible(true);
|
||||
else {
|
||||
form.submit();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setUpdateLoading(true);
|
||||
//let adjustmentsToInsert = {};
|
||||
|
||||
const { billlines, upload, ...bill } = values;
|
||||
const updates = [];
|
||||
updates.push(
|
||||
update_bill({
|
||||
variables: { billId: search.billid, bill: bill },
|
||||
})
|
||||
);
|
||||
|
||||
billlines.forEach((l) => {
|
||||
delete l.selected;
|
||||
});
|
||||
|
||||
// ... rest of the code remains the same
|
||||
//Find bill lines that were deleted.
|
||||
const deletedJobLines = [];
|
||||
|
||||
const handleSave = () => {
|
||||
//It's got a previously deducted bill line!
|
||||
if (
|
||||
data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 ||
|
||||
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
|
||||
0
|
||||
)
|
||||
setOpen(true);
|
||||
else {
|
||||
form.submit();
|
||||
}
|
||||
};
|
||||
data.bills_by_pk.billlines.forEach((a) => {
|
||||
const matchingRecord = billlines.find((b) => b.id === a.id);
|
||||
if (!matchingRecord) {
|
||||
deletedJobLines.push(a);
|
||||
}
|
||||
});
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setUpdateLoading(true);
|
||||
//let adjustmentsToInsert = {};
|
||||
deletedJobLines.forEach((d) => {
|
||||
updates.push(deleteBillLine({ variables: { id: d.id } }));
|
||||
});
|
||||
|
||||
const {billlines, upload, ...bill} = values;
|
||||
const updates = [];
|
||||
billlines.forEach((billline) => {
|
||||
const { deductedfromlbr, inventories, jobline, ...il } = billline;
|
||||
delete il.__typename;
|
||||
|
||||
if (il.id) {
|
||||
updates.push(
|
||||
update_bill({
|
||||
variables: {billId: search.billid, bill: bill},
|
||||
})
|
||||
updateBillLine({
|
||||
variables: {
|
||||
billLineId: il.id,
|
||||
billLine: {
|
||||
...il,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
joblineid: il.joblineid === "noline" ? null : il.joblineid,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
//It's a new line, have to insert it.
|
||||
updates.push(
|
||||
insertBillLine({
|
||||
variables: {
|
||||
billLines: [
|
||||
{
|
||||
...il,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
billid: search.billid,
|
||||
joblineid: il.joblineid === "noline" ? null : il.joblineid,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
billlines.forEach((l) => {
|
||||
delete l.selected;
|
||||
});
|
||||
await Promise.all(updates);
|
||||
|
||||
//Find bill lines that were deleted.
|
||||
const deletedJobLines = [];
|
||||
insertAuditTrail({
|
||||
jobid: bill.jobid,
|
||||
billid: search.billid,
|
||||
operation: AuditTrailMapping.billupdated(bill.invoice_number),
|
||||
});
|
||||
|
||||
data.bills_by_pk.billlines.forEach((a) => {
|
||||
const matchingRecord = billlines.find((b) => b.id === a.id);
|
||||
if (!matchingRecord) {
|
||||
deletedJobLines.push(a);
|
||||
}
|
||||
});
|
||||
await refetch();
|
||||
form.setFieldsValue(transformData(data));
|
||||
form.resetFields();
|
||||
setVisible(false);
|
||||
setUpdateLoading(false);
|
||||
};
|
||||
|
||||
deletedJobLines.forEach((d) => {
|
||||
updates.push(deleteBillLine({variables: {id: d.id}}));
|
||||
});
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||
|
||||
billlines.forEach((billline) => {
|
||||
const {deductedfromlbr, inventories, jobline, ...il} = billline;
|
||||
delete il.__typename;
|
||||
|
||||
if (il.id) {
|
||||
updates.push(
|
||||
updateBillLine({
|
||||
variables: {
|
||||
billLineId: il.id,
|
||||
billLine: {
|
||||
...il,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
joblineid: il.joblineid === "noline" ? null : il.joblineid,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
} else {
|
||||
//It's a new line, have to insert it.
|
||||
updates.push(
|
||||
insertBillLine({
|
||||
variables: {
|
||||
billLines: [
|
||||
{
|
||||
...il,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
billid: search.billid,
|
||||
joblineid: il.joblineid === "noline" ? null : il.joblineid,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(updates);
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: bill.jobid,
|
||||
billid: search.billid,
|
||||
operation: AuditTrailMapping.billupdated(bill.invoice_number),
|
||||
});
|
||||
|
||||
await refetch();
|
||||
form.setFieldsValue(transformData(data));
|
||||
form.resetFields();
|
||||
setOpen(false);
|
||||
setUpdateLoading(false);
|
||||
};
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||
|
||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -178,9 +179,9 @@ export function BillDetailEditcontainer({setPartsOrderContext, insertAuditTrail,
|
||||
<BillDetailEditReturn data={data} />
|
||||
<BillPrintButton billid={search.billid} />
|
||||
<Popconfirm
|
||||
open={open}
|
||||
visible={visible}
|
||||
onConfirm={() => form.submit()}
|
||||
onCancel={() => setOpen(false)}
|
||||
onCancel={() => setVisible(false)}
|
||||
okButtonProps={{ loading: updateLoading }}
|
||||
title={t("bills.labels.editadjwarning")}
|
||||
>
|
||||
@@ -207,45 +208,45 @@ export function BillDetailEditcontainer({setPartsOrderContext, insertAuditTrail,
|
||||
>
|
||||
<BillFormContainer form={form} billEdit disabled={exported} />
|
||||
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery
|
||||
job={{id: data ? data.bills_by_pk.jobid : null}}
|
||||
invoice_number={data ? data.bills_by_pk.invoice_number : null}
|
||||
vendorid={data ? data.bills_by_pk.vendorid : null}
|
||||
/>
|
||||
) : (
|
||||
<JobDocumentsGallery
|
||||
jobId={data ? data.bills_by_pk.jobid : null}
|
||||
billId={search.billid}
|
||||
documentsList={data ? data.bills_by_pk.documents : []}
|
||||
billsCallback={refetch}
|
||||
/>
|
||||
)}
|
||||
</Form>
|
||||
</>
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery
|
||||
job={{ id: data ? data.bills_by_pk.jobid : null }}
|
||||
invoice_number={data ? data.bills_by_pk.invoice_number : null}
|
||||
vendorid={data ? data.bills_by_pk.vendorid : null}
|
||||
/>
|
||||
) : (
|
||||
<JobDocumentsGallery
|
||||
jobId={data ? data.bills_by_pk.jobid : null}
|
||||
billId={search.billid}
|
||||
documentsList={data ? data.bills_by_pk.documents : []}
|
||||
billsCallback={refetch}
|
||||
/>
|
||||
)}
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const transformData = (data) => {
|
||||
return data
|
||||
? {
|
||||
...data.bills_by_pk,
|
||||
return data
|
||||
? {
|
||||
...data.bills_by_pk,
|
||||
|
||||
billlines: data.bills_by_pk.billlines.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
joblineid: !!i.joblineid ? i.joblineid : "noline",
|
||||
applicable_taxes: {
|
||||
federal:
|
||||
(i.applicable_taxes && i.applicable_taxes.federal) || false,
|
||||
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
|
||||
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
date: data.bills_by_pk ? dayjs(data.bills_by_pk.date) : null,
|
||||
}
|
||||
: {};
|
||||
billlines: data.bills_by_pk.billlines.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
joblineid: !!i.joblineid ? i.joblineid : "noline",
|
||||
applicable_taxes: {
|
||||
federal:
|
||||
(i.applicable_taxes && i.applicable_taxes.federal) || false,
|
||||
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
|
||||
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
|
||||
}
|
||||
: {};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import queryString from "query-string";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
@@ -33,10 +33,10 @@ export function BillDetailEditReturn({
|
||||
disabled,
|
||||
}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useNavigate();
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const handleFinish = ({ billlines }) => {
|
||||
const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id);
|
||||
@@ -67,18 +67,18 @@ export function BillDetailEditReturn({
|
||||
});
|
||||
delete search.billid;
|
||||
|
||||
history({ search: queryString.stringify(search) });
|
||||
setOpen(false);
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
setVisible(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (open === false) form.resetFields();
|
||||
}, [open, form]);
|
||||
if (visible === false) form.resetFields();
|
||||
}, [visible, form]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
open={open}
|
||||
onCancel={() => setOpen(false)}
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
destroyOnClose
|
||||
title={t("bills.actions.return")}
|
||||
onOk={() => form.submit()}
|
||||
@@ -175,7 +175,7 @@ export function BillDetailEditReturn({
|
||||
<Button
|
||||
disabled={data.bills_by_pk.is_credit_memo || disabled}
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setVisible(true);
|
||||
}}
|
||||
>
|
||||
{t("bills.actions.return")}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Drawer, Grid } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import BillDetailEditComponent from "./bill-detail-edit-component";
|
||||
|
||||
export default function BillDetailEditcontainer() {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useNavigate();
|
||||
const history = useHistory();
|
||||
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
@@ -29,10 +29,10 @@ export default function BillDetailEditcontainer() {
|
||||
width={drawerPercentage}
|
||||
onClose={() => {
|
||||
delete search.billid;
|
||||
history({ search: queryString.stringify(search) });
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}}
|
||||
destroyOnClose
|
||||
open={search.billid}
|
||||
visible={search.billid}
|
||||
>
|
||||
<BillDetailEditComponent />
|
||||
</Drawer>
|
||||
|
||||
@@ -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";
|
||||
@@ -63,9 +64,19 @@ function BillEnterModalContainer({
|
||||
"enter_bill_generate_label",
|
||||
false
|
||||
);
|
||||
const { Enhanced_Payroll } = useTreatments(
|
||||
["Enhanced_Payroll"],
|
||||
{},
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
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:
|
||||
@@ -94,10 +105,12 @@ function BillEnterModalContainer({
|
||||
location,
|
||||
outstanding_returns,
|
||||
inventory,
|
||||
federal_tax_exempt,
|
||||
...remainingValues
|
||||
} = values;
|
||||
|
||||
let adjustmentsToInsert = {};
|
||||
let payrollAdjustmentsToInsert = [];
|
||||
|
||||
const r1 = await insertBill({
|
||||
variables: {
|
||||
@@ -113,14 +126,33 @@ function BillEnterModalContainer({
|
||||
lbr_adjustment,
|
||||
location: lineLocation,
|
||||
part_type,
|
||||
create_ppc,
|
||||
original_actual_price,
|
||||
...restI
|
||||
} = i;
|
||||
|
||||
if (deductedfromlbr) {
|
||||
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
|
||||
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
|
||||
restI.actual_price / lbr_adjustment.rate;
|
||||
if (Enhanced_Payroll.treatment === "on") {
|
||||
if (
|
||||
deductedfromlbr &&
|
||||
true //payroll is on
|
||||
) {
|
||||
payrollAdjustmentsToInsert.push({
|
||||
id: i.joblineid,
|
||||
convertedtolbr: true,
|
||||
convertedtolbr_data: {
|
||||
mod_lb_hrs: lbr_adjustment.mod_lb_hrs * -1,
|
||||
mod_lbr_ty: lbr_adjustment.mod_lbr_ty,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (deductedfromlbr) {
|
||||
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
|
||||
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
|
||||
restI.actual_price / lbr_adjustment.rate;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...restI,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
@@ -146,6 +178,20 @@ function BillEnterModalContainer({
|
||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
payrollAdjustmentsToInsert.map((li) => {
|
||||
return updateJobLines({
|
||||
variables: {
|
||||
lineId: li.id,
|
||||
line: {
|
||||
convertedtolbr: li.convertedtolbr,
|
||||
convertedtolbr_data: li.convertedtolbr_data,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const adjKeys = Object.keys(adjustmentsToInsert);
|
||||
if (adjKeys.length > 0) {
|
||||
//Query the adjustments, merge, and update them.
|
||||
@@ -254,6 +300,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,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -322,12 +376,12 @@ function BillEnterModalContainer({
|
||||
});
|
||||
|
||||
if (enterAgain) {
|
||||
form.resetFields();
|
||||
form.resetFields();
|
||||
// form.resetFields();
|
||||
form.setFieldsValue({
|
||||
...formValues,
|
||||
billlines: [],
|
||||
});
|
||||
form.resetFields();
|
||||
} else {
|
||||
toggleModalVisible();
|
||||
}
|
||||
@@ -346,18 +400,18 @@ function BillEnterModalContainer({
|
||||
}, [enterAgain, form]);
|
||||
|
||||
useEffect(() => {
|
||||
if (billEnterModal.open) {
|
||||
if (billEnterModal.visible) {
|
||||
form.setFieldsValue(formValues);
|
||||
} else {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [billEnterModal.open, form, formValues]);
|
||||
}, [billEnterModal.visible, form, formValues]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("bills.labels.new")}
|
||||
width={"98%"}
|
||||
open={billEnterModal.open}
|
||||
visible={billEnterModal.visible}
|
||||
okText={t("general.actions.save")}
|
||||
keyboard="false"
|
||||
onOk={() => form.submit()}
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
import Icon, {UploadOutlined} from "@ant-design/icons";
|
||||
import {useApolloClient} from "@apollo/client";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import {Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload,} from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {MdOpenInNew} from "react-icons/md";
|
||||
import {connect} from "react-redux";
|
||||
import {Link} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {CHECK_BILL_INVOICE_NUMBER} from "../../graphql/bills.queries";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import Icon, { UploadOutlined } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import {
|
||||
Alert,
|
||||
Divider,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
Space,
|
||||
Statistic,
|
||||
Switch,
|
||||
Upload,
|
||||
} from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MdOpenInNew } from "react-icons/md";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
@@ -20,307 +30,323 @@ import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||
import BillFormLines from "./bill-form.lines.component";
|
||||
import {CalculateBillTotal} from "./bill-form.totals.utility";
|
||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export function BillFormComponent({bodyshop, disabled, form, vendorAutoCompleteOptions, lineData, responsibilityCenters, loadLines, billEdit, disableInvNumber, job, loadOutstandingReturns, loadInventory, preferredMake}) {
|
||||
export function BillFormComponent({
|
||||
bodyshop,
|
||||
disabled,
|
||||
form,
|
||||
vendorAutoCompleteOptions,
|
||||
lineData,
|
||||
responsibilityCenters,
|
||||
loadLines,
|
||||
billEdit,
|
||||
disableInvNumber,
|
||||
job,
|
||||
loadOutstandingReturns,
|
||||
loadInventory,
|
||||
preferredMake,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
const [discount, setDiscount] = useState(0);
|
||||
const { Extended_Bill_Posting } = useTreatments(
|
||||
["Extended_Bill_Posting"],
|
||||
{},
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
const { ClosingPeriod } = useTreatments(
|
||||
["ClosingPeriod"],
|
||||
{},
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const {t} = useTranslation();
|
||||
const client = useApolloClient();
|
||||
const [discount, setDiscount] = useState(0);
|
||||
const handleVendorSelect = (props, opt) => {
|
||||
setDiscount(opt.discount);
|
||||
|
||||
const { treatments: {Extended_Bill_Posting, ClosingPeriod} } = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Extended_Bill_Posting", "ClosingPeriod"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
|
||||
const handleVendorSelect = (props, opt) => {
|
||||
setDiscount(opt.discount);
|
||||
|
||||
opt &&
|
||||
!billEdit &&
|
||||
loadOutstandingReturns({
|
||||
variables: {
|
||||
jobId: form.getFieldValue("jobid"),
|
||||
vendorId: opt.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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 });
|
||||
opt &&
|
||||
!billEdit &&
|
||||
loadOutstandingReturns({
|
||||
variables: {
|
||||
jobId: form.getFieldValue("jobid"),
|
||||
vendorId: opt.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 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]);
|
||||
|
||||
useEffect(() => {
|
||||
const vendorId = form.getFieldValue("vendorid");
|
||||
if (vendorId && vendorAutoCompleteOptions) {
|
||||
const matchingVendors = vendorAutoCompleteOptions.filter(
|
||||
(v) => v.id === vendorId
|
||||
);
|
||||
if (matchingVendors.length === 1) {
|
||||
setDiscount(matchingVendors[0].discount);
|
||||
}
|
||||
}
|
||||
const jobId = form.getFieldValue("jobid");
|
||||
if (jobId) {
|
||||
loadLines({variables: {id: jobId}});
|
||||
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
|
||||
loadOutstandingReturns({
|
||||
useEffect(() => {
|
||||
const vendorId = form.getFieldValue("vendorid");
|
||||
if (vendorId && vendorAutoCompleteOptions) {
|
||||
const matchingVendors = vendorAutoCompleteOptions.filter(
|
||||
(v) => v.id === vendorId
|
||||
);
|
||||
if (matchingVendors.length === 1) {
|
||||
setDiscount(matchingVendors[0].discount);
|
||||
}
|
||||
}
|
||||
const jobId = form.getFieldValue("jobid");
|
||||
if (jobId) {
|
||||
loadLines({ variables: { id: jobId } });
|
||||
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
|
||||
loadOutstandingReturns({
|
||||
variables: {
|
||||
jobId: jobId,
|
||||
vendorId: vendorId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (vendorId === bodyshop.inhousevendorid && !billEdit) {
|
||||
loadInventory();
|
||||
}
|
||||
}, [
|
||||
form,
|
||||
billEdit,
|
||||
loadOutstandingReturns,
|
||||
loadInventory,
|
||||
setDiscount,
|
||||
vendorAutoCompleteOptions,
|
||||
loadLines,
|
||||
bodyshop.inhousevendorid,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormFieldsChanged form={form} />
|
||||
<Form.Item
|
||||
style={{ display: "none" }}
|
||||
name="isinhouse"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
name="jobid"
|
||||
label={t("bills.fields.ro_number")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<JobSearchSelect
|
||||
disabled={billEdit || disabled}
|
||||
convertedOnly
|
||||
notExported={false}
|
||||
onBlur={() => {
|
||||
if (form.getFieldValue("jobid") !== null) {
|
||||
loadLines({ variables: { id: form.getFieldValue("jobid") } });
|
||||
if (form.getFieldValue("vendorid") !== null) {
|
||||
loadOutstandingReturns({
|
||||
variables: {
|
||||
jobId: jobId,
|
||||
vendorId: vendorId,
|
||||
jobId: form.getFieldValue("jobid"),
|
||||
vendorId: form.getFieldValue("vendorid"),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bills.fields.vendor")}
|
||||
name="vendorid"
|
||||
// style={{ display: billEdit ? "none" : null }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
if (
|
||||
value &&
|
||||
!getFieldValue(["isinhouse"]) &&
|
||||
value === bodyshop.inhousevendorid
|
||||
) {
|
||||
return Promise.reject(t("bills.validation.manualinhouse"));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<VendorSearchSelect
|
||||
disabled={disabled}
|
||||
options={vendorAutoCompleteOptions}
|
||||
preferredMake={preferredMake}
|
||||
onSelect={handleVendorSelect}
|
||||
/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
{job &&
|
||||
job.ious &&
|
||||
job.ious.length > 0 &&
|
||||
job.ious.map((iou) => (
|
||||
<Alert
|
||||
key={iou.id}
|
||||
type="warning"
|
||||
message={
|
||||
<Space>
|
||||
{t("bills.labels.iouexists")}
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
to={`/manage/jobs/${iou.id}?tab=repairdata`}
|
||||
>
|
||||
<Space>
|
||||
{iou.ro_number}
|
||||
<Icon component={MdOpenInNew} />
|
||||
</Space>
|
||||
</Link>
|
||||
</Space>
|
||||
}
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("bills.fields.invoice_number")}
|
||||
name="invoice_number"
|
||||
validateTrigger="onBlur"
|
||||
hasFeedback
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
async validator(rule, value) {
|
||||
const vendorid = getFieldValue("vendorid");
|
||||
if (vendorid && value) {
|
||||
const response = await client.query({
|
||||
query: CHECK_BILL_INVOICE_NUMBER,
|
||||
variables: {
|
||||
invoice_number: value,
|
||||
vendorid: vendorid,
|
||||
},
|
||||
});
|
||||
|
||||
if (vendorId === bodyshop.inhousevendorid && !billEdit) {
|
||||
loadInventory();
|
||||
}
|
||||
}, [
|
||||
form,
|
||||
billEdit,
|
||||
loadOutstandingReturns,
|
||||
loadInventory,
|
||||
setDiscount,
|
||||
vendorAutoCompleteOptions,
|
||||
loadLines,
|
||||
bodyshop.inhousevendorid,
|
||||
]);
|
||||
if (response.data.bills_aggregate.aggregate.count === 0) {
|
||||
return Promise.resolve();
|
||||
} else if (
|
||||
response.data.bills_aggregate.nodes.length === 1 &&
|
||||
response.data.bills_aggregate.nodes[0].id ===
|
||||
form.getFieldValue("id")
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
t("bills.validation.unique_invoice_number")
|
||||
);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input disabled={disabled || disableInvNumber} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bills.fields.date")}
|
||||
name="date"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
if (
|
||||
ClosingPeriod.treatment === "on" &&
|
||||
bodyshop.accountingconfig.ClosingPeriod
|
||||
) {
|
||||
if (
|
||||
moment(value)
|
||||
.startOf("day")
|
||||
.isSameOrAfter(
|
||||
moment(
|
||||
bodyshop.accountingconfig.ClosingPeriod[0]
|
||||
).startOf("day")
|
||||
) &&
|
||||
moment(value)
|
||||
.startOf("day")
|
||||
.isSameOrBefore(
|
||||
moment(
|
||||
bodyshop.accountingconfig.ClosingPeriod[1]
|
||||
).endOf("day")
|
||||
)
|
||||
) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(t("bills.validation.closingperiod"));
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<FormDatePicker disabled={disabled} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bills.fields.is_credit_memo")}
|
||||
name="is_credit_memo"
|
||||
valuePropName="checked"
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
if (
|
||||
value === true &&
|
||||
getFieldValue("jobid") &&
|
||||
getFieldValue("vendorid")
|
||||
) {
|
||||
//Removed as this would cause an additional reload when validating the form on submit and clear the values.
|
||||
// loadOutstandingReturns({
|
||||
// variables: {
|
||||
// jobId: form.getFieldValue("jobid"),
|
||||
// vendorId: form.getFieldValue("vendorid"),
|
||||
// },
|
||||
// });
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormFieldsChanged form={form}/>
|
||||
<Form.Item
|
||||
style={{display: "none"}}
|
||||
name="isinhouse"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
name="jobid"
|
||||
label={t("bills.fields.ro_number")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<JobSearchSelect
|
||||
disabled={billEdit || disabled}
|
||||
convertedOnly
|
||||
notExported={false}
|
||||
onBlur={() => {
|
||||
if (form.getFieldValue("jobid") !== null) {
|
||||
loadLines({variables: {id: form.getFieldValue("jobid")}});
|
||||
if (form.getFieldValue("vendorid") !== null) {
|
||||
loadOutstandingReturns({
|
||||
variables: {
|
||||
jobId: form.getFieldValue("jobid"),
|
||||
vendorId: form.getFieldValue("vendorid"),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bills.fields.vendor")}
|
||||
name="vendorid"
|
||||
// style={{ display: billEdit ? "none" : null }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({getFieldValue}) => ({
|
||||
validator(rule, value) {
|
||||
if (
|
||||
value &&
|
||||
!getFieldValue(["isinhouse"]) &&
|
||||
value === bodyshop.inhousevendorid
|
||||
) {
|
||||
return Promise.reject(t("bills.validation.manualinhouse"));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<VendorSearchSelect
|
||||
disabled={disabled}
|
||||
options={vendorAutoCompleteOptions}
|
||||
preferredMake={preferredMake}
|
||||
onSelect={handleVendorSelect}
|
||||
/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
{job &&
|
||||
job.ious &&
|
||||
job.ious.length > 0 &&
|
||||
job.ious.map((iou) => (
|
||||
<Alert
|
||||
key={iou.id}
|
||||
type="warning"
|
||||
message={
|
||||
<Space>
|
||||
{t("bills.labels.iouexists")}
|
||||
<Link
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
to={`/manage/jobs/${iou.id}?tab=repairdata`}
|
||||
>
|
||||
<Space>
|
||||
{iou.ro_number}
|
||||
<Icon component={MdOpenInNew}/>
|
||||
</Space>
|
||||
</Link>
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("bills.fields.invoice_number")}
|
||||
name="invoice_number"
|
||||
validateTrigger="onBlur"
|
||||
hasFeedback
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({getFieldValue}) => ({
|
||||
async validator(rule, value) {
|
||||
const vendorid = getFieldValue("vendorid");
|
||||
if (vendorid && value) {
|
||||
const response = await client.query({
|
||||
query: CHECK_BILL_INVOICE_NUMBER,
|
||||
variables: {
|
||||
invoice_number: value,
|
||||
vendorid: vendorid,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.data.bills_aggregate.aggregate.count === 0) {
|
||||
return Promise.resolve();
|
||||
} else if (
|
||||
response.data.bills_aggregate.nodes.length === 1 &&
|
||||
response.data.bills_aggregate.nodes[0].id ===
|
||||
form.getFieldValue("id")
|
||||
) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(
|
||||
t("bills.validation.unique_invoice_number")
|
||||
);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input disabled={disabled || disableInvNumber}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bills.fields.date")}
|
||||
name="date"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
({getFieldValue}) => ({
|
||||
validator(rule, value) {
|
||||
if (
|
||||
ClosingPeriod.treatment === "on" &&
|
||||
bodyshop.accountingconfig.ClosingPeriod
|
||||
) {
|
||||
if (
|
||||
dayjs(value)
|
||||
.startOf("day")
|
||||
.isSameOrAfter(
|
||||
dayjs(
|
||||
bodyshop.accountingconfig.ClosingPeriod[0]
|
||||
).startOf("day")
|
||||
) &&
|
||||
dayjs(value)
|
||||
.startOf("day")
|
||||
.isSameOrBefore(
|
||||
dayjs(
|
||||
bodyshop.accountingconfig.ClosingPeriod[1]
|
||||
).endOf("day")
|
||||
)
|
||||
) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(t("bills.validation.closingperiod"));
|
||||
}
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<FormDatePicker disabled={disabled}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bills.fields.is_credit_memo")}
|
||||
name="is_credit_memo"
|
||||
valuePropName="checked"
|
||||
rules={[
|
||||
({getFieldValue}) => ({
|
||||
validator(rule, value) {
|
||||
if (
|
||||
value === true &&
|
||||
getFieldValue("jobid") &&
|
||||
getFieldValue("vendorid")
|
||||
) {
|
||||
//Removed as this would cause an additional reload when validating the form on submit and clear the values.
|
||||
// loadOutstandingReturns({
|
||||
// variables: {
|
||||
// jobId: form.getFieldValue("jobid"),
|
||||
// vendorId: form.getFieldValue("vendorid"),
|
||||
// },
|
||||
// });
|
||||
}
|
||||
|
||||
if (
|
||||
!bodyshop.bill_allow_post_to_closed &&
|
||||
job &&
|
||||
(job.status === bodyshop.md_ro_statuses.default_invoiced ||
|
||||
job.status === bodyshop.md_ro_statuses.default_exported ||
|
||||
job.status === bodyshop.md_ro_statuses.default_void) &&
|
||||
(value === false || !value)
|
||||
) {
|
||||
return Promise.reject(t("bills.labels.onlycmforinvoiced"));
|
||||
}
|
||||
if (
|
||||
!bodyshop.bill_allow_post_to_closed &&
|
||||
job &&
|
||||
(job.status === bodyshop.md_ro_statuses.default_invoiced ||
|
||||
job.status === bodyshop.md_ro_statuses.default_exported ||
|
||||
job.status === bodyshop.md_ro_statuses.default_void) &&
|
||||
(value === false || !value)
|
||||
) {
|
||||
return Promise.reject(t("bills.labels.onlycmforinvoiced"));
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
@@ -354,13 +380,15 @@ export function BillFormComponent({bodyshop, disabled, form, vendorAutoCompleteO
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.federal_tax_rate")}
|
||||
name="federal_tax_rate"
|
||||
>
|
||||
<CurrencyInput min={0} disabled={disabled} />
|
||||
</Form.Item>
|
||||
{
|
||||
// <Form.Item
|
||||
// span={3}
|
||||
// label={t("bills.fields.federal_tax_rate")}
|
||||
// name="federal_tax_rate"
|
||||
// >
|
||||
// <CurrencyInput min={0} disabled={disabled} />
|
||||
// </Form.Item>
|
||||
}
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.state_tax_rate")}
|
||||
@@ -368,22 +396,27 @@ export function BillFormComponent({bodyshop, disabled, form, vendorAutoCompleteO
|
||||
>
|
||||
<CurrencyInput min={0} disabled={disabled} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.local_tax_rate")}
|
||||
name="local_tax_rate"
|
||||
>
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
{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
|
||||
// span={3}
|
||||
// label={t("bills.fields.local_tax_rate")}
|
||||
// name="local_tax_rate"
|
||||
// >
|
||||
// <CurrencyInput min={0} />
|
||||
// </Form.Item>
|
||||
}
|
||||
{
|
||||
//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([
|
||||
@@ -409,21 +442,25 @@ export function BillFormComponent({bodyshop, disabled, form, vendorAutoCompleteO
|
||||
value={totals.subtotal.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("bills.labels.federal_tax")}
|
||||
value={totals.federalTax.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
{
|
||||
// <Statistic
|
||||
// title={t("bills.labels.federal_tax")}
|
||||
// value={totals.federalTax.toFormat()}
|
||||
// precision={2}
|
||||
// />
|
||||
}
|
||||
<Statistic
|
||||
title={t("bills.labels.state_tax")}
|
||||
value={totals.stateTax.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("bills.labels.local_tax")}
|
||||
value={totals.localTax.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
{
|
||||
// <Statistic
|
||||
// title={t("bills.labels.local_tax")}
|
||||
// value={totals.localTax.toFormat()}
|
||||
// precision={2}
|
||||
// />
|
||||
}
|
||||
<Statistic
|
||||
title={t("bills.labels.entered_total")}
|
||||
value={totals.enteredTotal.toFormat()}
|
||||
@@ -460,55 +497,55 @@ export function BillFormComponent({bodyshop, disabled, form, vendorAutoCompleteO
|
||||
</LayoutFormRow>
|
||||
<Divider orientation="left">{t("bills.labels.bill_lines")}</Divider>
|
||||
|
||||
{Extended_Bill_Posting.treatment === "on" ? (
|
||||
<BillFormLinesExtended
|
||||
lineData={lineData}
|
||||
discount={discount}
|
||||
form={form}
|
||||
responsibilityCenters={responsibilityCenters}
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : (
|
||||
<BillFormLines
|
||||
lineData={lineData}
|
||||
discount={discount}
|
||||
form={form}
|
||||
responsibilityCenters={responsibilityCenters}
|
||||
disabled={disabled}
|
||||
billEdit={billEdit}
|
||||
/>
|
||||
)}
|
||||
{Extended_Bill_Posting.treatment === "on" ? (
|
||||
<BillFormLinesExtended
|
||||
lineData={lineData}
|
||||
discount={discount}
|
||||
form={form}
|
||||
responsibilityCenters={responsibilityCenters}
|
||||
disabled={disabled}
|
||||
/>
|
||||
) : (
|
||||
<BillFormLines
|
||||
lineData={lineData}
|
||||
discount={discount}
|
||||
form={form}
|
||||
responsibilityCenters={responsibilityCenters}
|
||||
disabled={disabled}
|
||||
billEdit={billEdit}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
name="upload"
|
||||
label="Upload"
|
||||
style={{display: billEdit ? "none" : null}}
|
||||
valuePropName="fileList"
|
||||
getValueFromEvent={(e) => {
|
||||
if (Array.isArray(e)) {
|
||||
return e;
|
||||
}
|
||||
return e && e.fileList;
|
||||
}}
|
||||
>
|
||||
<Upload.Dragger
|
||||
multiple={true}
|
||||
name="logo"
|
||||
beforeUpload={() => false}
|
||||
listType="picture"
|
||||
>
|
||||
<>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<UploadOutlined/>
|
||||
</p>
|
||||
<p className="ant-upload-text">
|
||||
Click or drag files to this area to upload.
|
||||
</p>
|
||||
</>
|
||||
</Upload.Dragger>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
<Form.Item
|
||||
name="upload"
|
||||
label="Upload"
|
||||
style={{ display: billEdit ? "none" : null }}
|
||||
valuePropName="fileList"
|
||||
getValueFromEvent={(e) => {
|
||||
if (Array.isArray(e)) {
|
||||
return e;
|
||||
}
|
||||
return e && e.fileList;
|
||||
}}
|
||||
>
|
||||
<Upload.Dragger
|
||||
multiple={true}
|
||||
name="logo"
|
||||
beforeUpload={() => false}
|
||||
listType="picture"
|
||||
>
|
||||
<>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<UploadOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">
|
||||
Click or drag files to this area to upload.
|
||||
</p>
|
||||
</>
|
||||
</Upload.Dragger>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(BillFormComponent);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -23,11 +23,11 @@ export function BillFormContainer({
|
||||
disabled,
|
||||
disableInvNumber,
|
||||
}) {
|
||||
const { treatments: {Simple_Inventory} } = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Simple_Inventory"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid,
|
||||
});
|
||||
const { Simple_Inventory } = useTreatments(
|
||||
["Simple_Inventory"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const { data: VendorAutoCompleteData } = useQuery(
|
||||
SEARCH_VENDOR_AUTOCOMPLETE,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
@@ -41,13 +42,17 @@ export function BillEnterModalLinesComponent({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
||||
const { Simple_Inventory } = useTreatments(
|
||||
["Simple_Inventory"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const { treatments: {Simple_Inventory} } = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Simple_Inventory"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const { Enhanced_Payroll } = useTreatments(
|
||||
["Enhanced_Payroll"],
|
||||
{},
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const columns = (remove) => {
|
||||
return [
|
||||
@@ -97,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"
|
||||
@@ -223,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"),
|
||||
@@ -364,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",
|
||||
@@ -379,12 +422,31 @@ export function BillEnterModalLinesComponent({
|
||||
"rate",
|
||||
]);
|
||||
|
||||
const billline = getFieldValue(["billlines", record.name]);
|
||||
|
||||
const jobline = lineData.find(
|
||||
(line) => line.id === billline?.joblineid
|
||||
);
|
||||
|
||||
const employeeTeamName = bodyshop.employee_teams.find(
|
||||
(team) => team.id === jobline?.assigned_team
|
||||
);
|
||||
|
||||
if (getFieldValue(["billlines", record.name, "deductedfromlbr"]))
|
||||
return (
|
||||
<div>
|
||||
<Space>
|
||||
{t("joblines.fields.assigned_team", {
|
||||
name: employeeTeamName?.name,
|
||||
})}
|
||||
{`${jobline.mod_lb_hrs} units/${t(
|
||||
`joblines.fields.lbr_types.${jobline.mod_lbr_ty}`
|
||||
)}`}
|
||||
</Space>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.mod_lbr_ty")}
|
||||
key={`${index}modlbrty`}
|
||||
initialValue={jobline ? jobline.mod_lbr_ty : null}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
@@ -438,22 +500,44 @@ export function BillEnterModalLinesComponent({
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.labels.adjustmentrate")}
|
||||
name={[record.name, "lbr_adjustment", "rate"]}
|
||||
initialValue={bodyshop.default_adjustment_rate}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={2} min={0.01} />
|
||||
</Form.Item>
|
||||
{price &&
|
||||
adjustmentRate &&
|
||||
`${(price / adjustmentRate).toFixed(1)} hrs`}
|
||||
{Enhanced_Payroll.treatment === "on" ? (
|
||||
<Form.Item
|
||||
label={t("billlines.labels.mod_lbr_adjustment")}
|
||||
name={[record.name, "lbr_adjustment", "mod_lb_hrs"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
precision={5}
|
||||
min={0.01}
|
||||
max={jobline ? jobline.mod_lb_hrs : 0}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item
|
||||
label={t("jobs.labels.adjustmentrate")}
|
||||
name={[record.name, "lbr_adjustment", "rate"]}
|
||||
initialValue={bodyshop.default_adjustment_rate}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={2} min={0.01} />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Space>
|
||||
{price &&
|
||||
adjustmentRate &&
|
||||
`${(price / adjustmentRate).toFixed(1)} hrs`}
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
return <></>;
|
||||
@@ -461,22 +545,21 @@ export function BillEnterModalLinesComponent({
|
||||
</Form.Item>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.federal_tax_applicable"),
|
||||
dataIndex: "applicable_taxes.federal",
|
||||
editable: true,
|
||||
// {
|
||||
// title: t("billlines.fields.federal_tax_applicable"),
|
||||
// dataIndex: "applicable_taxes.federal",
|
||||
// editable: true,
|
||||
|
||||
formItemProps: (field) => {
|
||||
return {
|
||||
key: `${field.index}fedtax`,
|
||||
valuePropName: "checked",
|
||||
initialValue:
|
||||
form.getFieldValue("federal_tax_exempt") === true ? false : true,
|
||||
name: [field.name, "applicable_taxes", "federal"],
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
},
|
||||
// formItemProps: (field) => {
|
||||
// return {
|
||||
// key: `${field.index}fedtax`,
|
||||
// valuePropName: "checked",
|
||||
// // initialValue: true,
|
||||
// name: [field.name, "applicable_taxes", "federal"],
|
||||
// };
|
||||
// },
|
||||
// formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
// },
|
||||
{
|
||||
title: t("billlines.fields.state_tax_applicable"),
|
||||
dataIndex: "applicable_taxes.state",
|
||||
@@ -491,20 +574,20 @@ export function BillEnterModalLinesComponent({
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.local_tax_applicable"),
|
||||
dataIndex: "applicable_taxes.local",
|
||||
editable: true,
|
||||
// {
|
||||
// title: t("billlines.fields.local_tax_applicable"),
|
||||
// dataIndex: "applicable_taxes.local",
|
||||
// editable: true,
|
||||
|
||||
formItemProps: (field) => {
|
||||
return {
|
||||
key: `${field.index}localtax`,
|
||||
valuePropName: "checked",
|
||||
name: [field.name, "applicable_taxes", "local"],
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
},
|
||||
// formItemProps: (field) => {
|
||||
// return {
|
||||
// key: `${field.index}localtax`,
|
||||
// valuePropName: "checked",
|
||||
// name: [field.name, "applicable_taxes", "local"],
|
||||
// };
|
||||
// },
|
||||
// formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
// },
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
|
||||
@@ -629,7 +712,7 @@ const EditableCell = ({
|
||||
if (additional)
|
||||
return (
|
||||
<td {...restProps}>
|
||||
<Space size="small">
|
||||
<div size="small">
|
||||
<Form.Item
|
||||
name={dataIndex}
|
||||
labelCol={{ span: 0 }}
|
||||
@@ -638,7 +721,7 @@ const EditableCell = ({
|
||||
{(formInput && formInput(record, record.name)) || children}
|
||||
</Form.Item>
|
||||
{additional && additional(record, record.name)}
|
||||
</Space>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
if (wrapper)
|
||||
|
||||
@@ -19,14 +19,14 @@ export const CalculateBillTotal = (invoice) => {
|
||||
}).multiply(i.quantity || 1);
|
||||
|
||||
subtotal = subtotal.add(itemTotal);
|
||||
if (i.applicable_taxes.federal) {
|
||||
if (i.applicable_taxes?.federal) {
|
||||
federalTax = federalTax.add(
|
||||
itemTotal.percentage(federal_tax_rate || 0)
|
||||
);
|
||||
}
|
||||
if (i.applicable_taxes.state)
|
||||
if (i.applicable_taxes?.state)
|
||||
stateTax = stateTax.add(itemTotal.percentage(state_tax_rate || 0));
|
||||
if (i.applicable_taxes.local)
|
||||
if (i.applicable_taxes?.local)
|
||||
localTax = localTax.add(itemTotal.percentage(local_tax_rate || 0));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,8 +15,7 @@ const BillLineSearchSelect = (
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
showSearch
|
||||
popupMatchSelectWidth={false}
|
||||
optionLabelProp={"name"}
|
||||
dropdownMatchSelectWidth={false}
|
||||
// optionFilterProp="line_desc"
|
||||
filterOption={(inputValue, option) => {
|
||||
return (
|
||||
@@ -58,15 +57,18 @@ const BillLineSearchSelect = (
|
||||
style={{
|
||||
...(item.removed ? { textDecoration: "line-through" } : {}),
|
||||
}}
|
||||
name={`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
|
||||
>
|
||||
<span>
|
||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
|
||||
</span>
|
||||
{item.act_price === 0 && item.mod_lb_hrs > 0 && (
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{`${item.mod_lb_hrs} units`}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{item.act_price
|
||||
? `$${item.act_price && item.act_price.toFixed(2)}`
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FileAddFilled } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification, Tooltip } from "antd";
|
||||
import { t } from "i18next";
|
||||
import dayjs from "./../../utils/day";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -36,6 +36,7 @@ export function BilllineAddInventory({
|
||||
}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { billid } = queryString.parse(useLocation().search);
|
||||
|
||||
const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
|
||||
|
||||
const addToInventory = async () => {
|
||||
@@ -49,7 +50,7 @@ export function BilllineAddInventory({
|
||||
jobid: jobid,
|
||||
isinhouse: true,
|
||||
is_credit_memo: true,
|
||||
date: dayjs().format("YYYY-MM-DD"),
|
||||
date: moment().format("YYYY-MM-DD"),
|
||||
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate,
|
||||
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate,
|
||||
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate,
|
||||
@@ -91,7 +92,7 @@ export function BilllineAddInventory({
|
||||
pol: {
|
||||
returnfrombill: billid,
|
||||
vendorid: bodyshop.inhousevendorid,
|
||||
deliver_by: dayjs().format("YYYY-MM-DD"),
|
||||
deliver_by: moment().format("YYYY-MM-DD"),
|
||||
parts_order_lines: {
|
||||
data: [
|
||||
{
|
||||
|
||||
@@ -9,8 +9,8 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
|
||||
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
@@ -58,7 +58,7 @@ export function BillsListTableComponent({
|
||||
<EditFilled />
|
||||
</Button>
|
||||
)}
|
||||
<BillDeleteButton bill={record} />
|
||||
<BillDeleteButton bill={record} jobid={job.id} />
|
||||
<BillDetailEditReturnComponent
|
||||
data={{ bills_by_pk: { ...record, jobid: job.id } }}
|
||||
disabled={
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import queryString from "query-string";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import { Table, Input } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
@@ -10,7 +10,7 @@ import AlertComponent from "../alert/alert.component";
|
||||
|
||||
export default function BillsVendorsList() {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const history = useNavigate();
|
||||
const history = useHistory();
|
||||
|
||||
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS, {
|
||||
fetchPolicy: "network-only",
|
||||
|
||||
@@ -1,64 +1,54 @@
|
||||
import {HomeFilled} from "@ant-design/icons";
|
||||
import {Breadcrumb, Col, Row} from "antd";
|
||||
import { HomeFilled } from "@ant-design/icons";
|
||||
import { Breadcrumb, Row, Col } from "antd";
|
||||
import React from "react";
|
||||
import {connect} from "react-redux";
|
||||
import {Link} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBreadcrumbs} from "../../redux/application/application.selectors";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import GlobalSearch from "../global-search/global-search.component";
|
||||
import GlobalSearchOs from "../global-search/global-search-os.component";
|
||||
import "./breadcrumbs.styles.scss";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
breadcrumbs: selectBreadcrumbs,
|
||||
bodyshop: selectBodyshop,
|
||||
breadcrumbs: selectBreadcrumbs,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export function BreadCrumbs({breadcrumbs, bodyshop}) {
|
||||
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
||||
const { OpenSearch } = useTreatments(
|
||||
["OpenSearch"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const {treatments: {OpenSearch}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["OpenSearch"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid,
|
||||
});
|
||||
// TODO - Client Update - Technically key is not doing anything here
|
||||
return (
|
||||
<Row className="breadcrumb-container">
|
||||
<Col xs={24} sm={24} md={16}>
|
||||
<Breadcrumb
|
||||
separator=">"
|
||||
items={[
|
||||
{
|
||||
key: "home",
|
||||
title: (
|
||||
<Link to={`/manage/`}>
|
||||
<HomeFilled/>{" "}
|
||||
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
|
||||
""}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
...breadcrumbs.map((item) =>
|
||||
item.link
|
||||
? {
|
||||
key: item.label,
|
||||
title: <Link to={item.link}>{item.label}</Link>,
|
||||
}
|
||||
: {
|
||||
key: item.label,
|
||||
title: item.label,
|
||||
}
|
||||
),
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={8}>
|
||||
{OpenSearch.treatment === "on" ? <GlobalSearchOs/> : <GlobalSearch/>}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
return (
|
||||
<Row className="breadcrumb-container">
|
||||
<Col xs={24} sm={24} md={16}>
|
||||
<Breadcrumb separator=">">
|
||||
<Breadcrumb.Item>
|
||||
<Link to={`/manage`}>
|
||||
<HomeFilled />{" "}
|
||||
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
|
||||
""}
|
||||
</Link>
|
||||
</Breadcrumb.Item>
|
||||
{breadcrumbs.map((item) =>
|
||||
item.link ? (
|
||||
<Breadcrumb.Item key={item.label}>
|
||||
<Link to={item.link}>{item.label} </Link>
|
||||
</Breadcrumb.Item>
|
||||
) : (
|
||||
<Breadcrumb.Item key={item.label}>{item.label}</Breadcrumb.Item>
|
||||
)
|
||||
)}
|
||||
</Breadcrumb>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={8}>
|
||||
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(BreadCrumbs);
|
||||
|
||||
@@ -25,7 +25,7 @@ export function ContractsFindModalContainer({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { open } = caBcEtfTableModal;
|
||||
const { visible } = caBcEtfTableModal;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const EtfTemplate = TemplateList("special").ca_bc_etf_table;
|
||||
@@ -63,14 +63,14 @@ export function ContractsFindModalContainer({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (visible) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [open, form]);
|
||||
}, [visible, form]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
visible={visible}
|
||||
width="70%"
|
||||
title={t("payments.labels.findermodal")}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
||||
<Popover
|
||||
destroyTooltipOnHide
|
||||
content={popContent}
|
||||
open={visibility}
|
||||
visible={visibility}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
notification,
|
||||
} from "antd";
|
||||
import axios from "axios";
|
||||
import dayjs from "../../utils/day";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -117,7 +117,7 @@ const CardPaymentModalComponent = ({
|
||||
payer: t("payments.labels.customer"),
|
||||
type: values.paymentResponse.cardbrand,
|
||||
jobid: payment.jobid,
|
||||
date: dayjs(Date.now()),
|
||||
date: moment(Date.now()),
|
||||
payment_responses: {
|
||||
data: [
|
||||
{
|
||||
|
||||
@@ -22,7 +22,7 @@ function CardPaymentModalContainer({
|
||||
toggleModalVisible,
|
||||
bodyshop,
|
||||
}) {
|
||||
const { open } = cardPaymentModal;
|
||||
const { visible } = cardPaymentModal;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCancel = () => {
|
||||
@@ -35,7 +35,7 @@ function CardPaymentModalContainer({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
open={visible}
|
||||
onOk={handleOK}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
|
||||
@@ -4,11 +4,20 @@ import { Button, notification, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { messaging, requestForToken } from "../../firebase/firebase.utils";
|
||||
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import FcmHandler from "../../utils/fcm-handler";
|
||||
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
||||
import "./chat-affix.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
chatVisible: selectChatVisible,
|
||||
});
|
||||
|
||||
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -27,34 +36,35 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||
console.log("FCM Topic Subscription", r.data);
|
||||
} catch (error) {
|
||||
console.log(
|
||||
"Error attempting to subscribe to messaging topic: ",
|
||||
error
|
||||
"Error attempting to subscribe to messaging topic: ",
|
||||
error
|
||||
);
|
||||
notification.open({
|
||||
type: "warning",
|
||||
message: t("general.errors.fcm"),
|
||||
btn: (
|
||||
<Space>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await requestForToken();
|
||||
SubscribeToTopic();
|
||||
}}
|
||||
>
|
||||
{t("general.actions.tryagain")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const win = window.open(
|
||||
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
|
||||
"_blank"
|
||||
);
|
||||
win.focus();
|
||||
}}
|
||||
>
|
||||
{t("general.labels.help")}
|
||||
</Button>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
await requestForToken();
|
||||
|
||||
SubscribeToTopic();
|
||||
}}
|
||||
>
|
||||
{t("general.actions.tryagain")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const win = window.open(
|
||||
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
|
||||
"_blank"
|
||||
);
|
||||
win.focus();
|
||||
}}
|
||||
>
|
||||
{t("general.labels.help")}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -71,16 +81,16 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||
payload: (payload && payload.data && payload.data.data) || payload.data,
|
||||
});
|
||||
}
|
||||
let stopMessageListener, channel;
|
||||
let stopMessageListenr, channel;
|
||||
try {
|
||||
stopMessageListener = onMessage(messaging, handleMessage);
|
||||
stopMessageListenr = onMessage(messaging, handleMessage);
|
||||
channel = new BroadcastChannel("imex-sw-messages");
|
||||
channel.addEventListener("message", handleMessage);
|
||||
} catch (error) {
|
||||
console.log("Unable to set event listeners.");
|
||||
}
|
||||
return () => {
|
||||
stopMessageListener && stopMessageListener();
|
||||
stopMessageListenr && stopMessageListenr();
|
||||
channel && channel.removeEventListener("message", handleMessage);
|
||||
};
|
||||
}, [client]);
|
||||
@@ -88,10 +98,9 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
|
||||
|
||||
return (
|
||||
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
||||
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
|
||||
</div>
|
||||
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
||||
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChatAffixContainer;
|
||||
export default connect(mapStateToProps, null)(ChatAffixContainer);
|
||||
|
||||
@@ -1,122 +1,120 @@
|
||||
import {Badge, Card, List, Space, Tag} from "antd";
|
||||
import { Badge, List, Tag } from "antd";
|
||||
import React from "react";
|
||||
import {connect} from "react-redux";
|
||||
import {AutoSizer, CellMeasurer, CellMeasurerCache, List as VirtualizedList,} from "react-virtualized";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {setSelectedConversation} from "../../redux/messaging/messaging.actions";
|
||||
import {selectSelectedConversation} from "../../redux/messaging/messaging.selectors";
|
||||
import {TimeAgoFormatter} from "../../utils/DateFormatter";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
AutoSizer,
|
||||
CellMeasurer,
|
||||
CellMeasurerCache,
|
||||
List as VirtualizedList,
|
||||
} from "react-virtualized";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
|
||||
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||
import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component";
|
||||
import _ from "lodash";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
import "./chat-conversation-list.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
selectedConversation: selectSelectedConversation,
|
||||
selectedConversation: selectSelectedConversation,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setSelectedConversation: (conversationId) =>
|
||||
dispatch(setSelectedConversation(conversationId)),
|
||||
setSelectedConversation: (conversationId) =>
|
||||
dispatch(setSelectedConversation(conversationId)),
|
||||
});
|
||||
|
||||
function ChatConversationListComponent({
|
||||
conversationList,
|
||||
selectedConversation,
|
||||
setSelectedConversation,
|
||||
loadMoreConversations,
|
||||
}) {
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 60,
|
||||
});
|
||||
conversationList,
|
||||
selectedConversation,
|
||||
setSelectedConversation,
|
||||
loadMoreConversations,
|
||||
}) {
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 60,
|
||||
});
|
||||
|
||||
const rowRenderer = ({index, key, style, parent}) => {
|
||||
const item = conversationList[index];
|
||||
const cardContentRight =
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>;
|
||||
const cardContentLeft = item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => (
|
||||
<Tag key={idx}>{j.job.ro_number}</Tag>
|
||||
))
|
||||
: null;
|
||||
|
||||
const names = <>{_.uniq(item.job_conversations.map((j, idx) =>
|
||||
OwnerNameDisplayFunction(j.job)
|
||||
))}</>
|
||||
|
||||
const cardTitle = <>
|
||||
{item.label && <Tag color="blue">{item.label}</Tag>}
|
||||
{item.job_conversations.length > 0 ? (
|
||||
<Space direction="vertical">
|
||||
{names}
|
||||
</Space>
|
||||
) : (
|
||||
<Space>
|
||||
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
|
||||
</Space>
|
||||
)}
|
||||
</>
|
||||
const cardExtra = <Badge count={item.messages_aggregate.aggregate.count || 0}/>
|
||||
|
||||
const getCardStyle = () =>
|
||||
item.id === selectedConversation
|
||||
? { backgroundColor: 'rgba(128, 128, 128, 0.2)' }
|
||||
: { backgroundColor: index % 2 === 0 ? '#f0f2f5' : '#ffffff' };
|
||||
|
||||
return (
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
cache={cache}
|
||||
parent={parent}
|
||||
columnIndex={0}
|
||||
rowIndex={index}
|
||||
>
|
||||
<List.Item
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
style={style}
|
||||
className={`chat-list-item
|
||||
${
|
||||
item.id === selectedConversation
|
||||
? "chat-list-selected-conversation"
|
||||
: null
|
||||
}`}
|
||||
>
|
||||
<Card style={getCardStyle()} bordered={false} size="small" extra={cardExtra} title={cardTitle}>
|
||||
<div style={{display: 'inline-block', width: '70%', textAlign: 'left'}}>
|
||||
{cardContentLeft}
|
||||
</div>
|
||||
<div
|
||||
style={{display: 'inline-block', width: '30%', textAlign: 'right'}}>{cardContentRight}</div>
|
||||
</Card>
|
||||
</List.Item>
|
||||
</CellMeasurer>
|
||||
);
|
||||
};
|
||||
const rowRenderer = ({ index, key, style, parent }) => {
|
||||
const item = conversationList[index];
|
||||
|
||||
return (
|
||||
<div className="chat-list-container">
|
||||
<AutoSizer>
|
||||
{({height, width}) => (
|
||||
<VirtualizedList
|
||||
height={height}
|
||||
width={width}
|
||||
rowCount={conversationList.length}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
onScroll={({scrollTop, scrollHeight, clientHeight}) => {
|
||||
if (scrollTop + clientHeight === scrollHeight) {
|
||||
loadMoreConversations();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
cache={cache}
|
||||
parent={parent}
|
||||
columnIndex={0}
|
||||
rowIndex={index}
|
||||
>
|
||||
<List.Item
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
className={`chat-list-item ${
|
||||
item.id === selectedConversation
|
||||
? "chat-list-selected-conversation"
|
||||
: null
|
||||
}`}
|
||||
style={style}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
{item.label && <div className="chat-name">{item.label}</div>}
|
||||
{item.job_conversations.length > 0 ? (
|
||||
<div className="chat-name">
|
||||
{item.job_conversations.map((j, idx) => (
|
||||
<div key={idx}>
|
||||
<OwnerNameDisplay ownerObject={j.job} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: "inline-block" }}>
|
||||
<div>
|
||||
{item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => (
|
||||
<Tag key={idx} className="ro-number-tag">
|
||||
{j.job.ro_number}
|
||||
</Tag>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
</div>
|
||||
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
||||
</List.Item>
|
||||
</CellMeasurer>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-list-container">
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<VirtualizedList
|
||||
height={height}
|
||||
width={width}
|
||||
rowCount={conversationList.length}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
|
||||
if (scrollTop + clientHeight === scrollHeight) {
|
||||
loadMoreConversations();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ChatConversationListComponent);
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
.chat-list-selected-conversation {
|
||||
background-color: rgba(128, 128, 128, 0.2);
|
||||
}
|
||||
.chat-list-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
border: 1px solid gainsboro;
|
||||
}
|
||||
|
||||
.chat-list-item {
|
||||
.ant-card-head {
|
||||
border: none;
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: #ff7a00;
|
||||
}
|
||||
.chat-name {
|
||||
flex: 1;
|
||||
display: inline;
|
||||
}
|
||||
.ro-number-tag {
|
||||
align-self: baseline;
|
||||
}
|
||||
padding: 12px 24px;
|
||||
border-bottom: 1px solid gainsboro;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function ChatMediaSelector({
|
||||
conversation,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
|
||||
fetchPolicy: "network-only",
|
||||
@@ -39,13 +39,13 @@ export function ChatMediaSelector({
|
||||
},
|
||||
|
||||
skip:
|
||||
!open ||
|
||||
!visible ||
|
||||
!conversation.job_conversations ||
|
||||
conversation.job_conversations.length === 0,
|
||||
});
|
||||
|
||||
const handleVisibleChange = (change) => {
|
||||
setOpen(change);
|
||||
const handleVisibleChange = (visible) => {
|
||||
setVisible(visible);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -65,7 +65,7 @@ export function ChatMediaSelector({
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
/>
|
||||
)}
|
||||
{bodyshop.uselocalmediaserver && open && (
|
||||
{bodyshop.uselocalmediaserver && visible && (
|
||||
<JobDocumentsLocalGalleryExternal
|
||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||
jobId={
|
||||
@@ -88,8 +88,8 @@ export function ChatMediaSelector({
|
||||
}
|
||||
title={t("messaging.labels.selectmedia")}
|
||||
trigger="click"
|
||||
open={open}
|
||||
onOpenChange={handleVisibleChange}
|
||||
visible={visible}
|
||||
onVisibleChange={handleVisibleChange}
|
||||
>
|
||||
<Badge count={selectedMedia.filter((s) => s.isSelected).length}>
|
||||
<PictureFilled style={{ margin: "0 .5rem" }} />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Icon from "@ant-design/icons";
|
||||
import { Tooltip } from "antd";
|
||||
import i18n from "i18next";
|
||||
import dayjs from "../../utils/day";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { MdDone, MdDoneAll } from "react-icons/md";
|
||||
import {
|
||||
@@ -52,7 +52,7 @@ export default function ChatMessageListComponent({ messages }) {
|
||||
<div style={{ fontSize: 10 }}>
|
||||
{i18n.t("messaging.labels.sentby", {
|
||||
by: messages[index].userid,
|
||||
time: dayjs(messages[index].created_at).format(
|
||||
time: moment(messages[index].created_at).format(
|
||||
"MM/DD/YYYY @ hh:mm a"
|
||||
),
|
||||
})}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PlusCircleOutlined } from "@ant-design/icons";
|
||||
import { Dropdown } from "antd";
|
||||
import { Dropdown, Menu } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -16,16 +16,19 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
|
||||
export function ChatPresetsComponent({ bodyshop, setMessage, className }) {
|
||||
|
||||
const items = bodyshop.md_messaging_presets.map((i, idx) => ({
|
||||
key: idx,
|
||||
label: (i.label),
|
||||
onClick: () => setMessage(i.text),
|
||||
}));
|
||||
const menu = (
|
||||
<Menu>
|
||||
{bodyshop.md_messaging_presets.map((i, idx) => (
|
||||
<Menu.Item onClick={() => setMessage(i.text)} key={idx}>
|
||||
{i.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Dropdown trigger={["click"]} menu={{items}}>
|
||||
<Dropdown trigger={["click"]} overlay={menu}>
|
||||
<PlusCircleOutlined />
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -16,28 +16,42 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
export function ChatPrintButton({ conversation }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const generateDocument = (type) => {
|
||||
setLoading(true);
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("messaging").conversation_list.key,
|
||||
variables: { id: conversation.id },
|
||||
},
|
||||
{
|
||||
subject: TemplateList("messaging").conversation_list.subject,
|
||||
},
|
||||
type,
|
||||
conversation.id
|
||||
).catch(e => {
|
||||
console.warn('Something went wrong generating a document.');
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<Space wrap>
|
||||
<PrinterOutlined onClick={() => generateDocument('p')}/>
|
||||
<MailOutlined onClick={() => generateDocument('e')}/>
|
||||
<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>
|
||||
);
|
||||
|
||||
@@ -9,17 +9,17 @@ export default function ChatTagRoComponent({
|
||||
loading,
|
||||
handleSearch,
|
||||
handleInsertTag,
|
||||
setOpen,
|
||||
setVisible,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<Space flex>
|
||||
<div style={{ width: "15rem" }}>
|
||||
<Select
|
||||
showSearch
|
||||
autoFocus
|
||||
popupMatchSelectWidth
|
||||
dropdownMatchSelectWidth
|
||||
placeholder={t("general.labels.search")}
|
||||
filterOption={false}
|
||||
onSearch={handleSearch}
|
||||
@@ -38,7 +38,7 @@ export default function ChatTagRoComponent({
|
||||
{loading ? (
|
||||
<LoadingOutlined />
|
||||
) : (
|
||||
<CloseCircleOutlined onClick={() => setOpen(false)} />
|
||||
<CloseCircleOutlined onClick={() => setVisible(false)} />
|
||||
)}
|
||||
</Space>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ import ChatTagRo from "./chat-tag-ro.component";
|
||||
|
||||
export default function ChatTagRoContainer({ conversation }) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const [loadRo, { loading, data }] = useLazyQuery(SEARCH_FOR_JOBS);
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function ChatTagRoContainer({ conversation }) {
|
||||
const handleInsertTag = (value, option) => {
|
||||
logImEXEvent("messaging_add_job_tag");
|
||||
insertTag({ variables: { jobId: option.key } });
|
||||
setOpen(false);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const existingJobTags =
|
||||
@@ -47,16 +47,16 @@ export default function ChatTagRoContainer({ conversation }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{open ? (
|
||||
{visible ? (
|
||||
<ChatTagRo
|
||||
loading={loading}
|
||||
roOptions={roOptions}
|
||||
handleSearch={handleSearch}
|
||||
handleInsertTag={handleInsertTag}
|
||||
setOpen={setOpen}
|
||||
setVisible={setVisible}
|
||||
/>
|
||||
) : (
|
||||
<Tag onClick={() => setOpen(true)}>
|
||||
<Tag onClick={() => setVisible(true)}>
|
||||
<PlusOutlined />
|
||||
{t("messaging.actions.link")}
|
||||
</Tag>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import dayjs from "../../utils/day";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { QUERY_AVAILABLE_CC } from "../../graphql/courtesy-car.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -7,7 +7,7 @@ import ContractCarsComponent from "./contract-cars.component";
|
||||
|
||||
export default function ContractCarsContainer({ selectedCarState, form }) {
|
||||
const { loading, error, data } = useQuery(QUERY_AVAILABLE_CC, {
|
||||
variables: { today: dayjs().format("YYYY-MM-DD") },
|
||||
variables: { today: moment().format("YYYY-MM-DD") },
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
Space,
|
||||
} from "antd";
|
||||
import axios from "axios";
|
||||
import dayjs from "../../utils/day";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_NEW_JOB } from "../../graphql/jobs.queries";
|
||||
import {
|
||||
@@ -38,17 +38,17 @@ export function ContractConvertToRo({
|
||||
disabled,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [insertJob] = useMutation(INSERT_NEW_JOB);
|
||||
const history = useNavigate();
|
||||
const history = useHistory();
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setLoading(true);
|
||||
|
||||
const contractLength = dayjs(contract.actualreturn).diff(
|
||||
dayjs(contract.start),
|
||||
"day"
|
||||
const contractLength = moment(contract.actualreturn).diff(
|
||||
moment(contract.start),
|
||||
"days"
|
||||
);
|
||||
const billingLines = [];
|
||||
if (contractLength > 0)
|
||||
@@ -306,7 +306,7 @@ export function ContractConvertToRo({
|
||||
});
|
||||
}
|
||||
|
||||
setOpen(false);
|
||||
setVisible(false);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
@@ -380,7 +380,7 @@ export function ContractConvertToRo({
|
||||
<Button type="primary" htmlType="submit" loading={loading}>
|
||||
{t("contracts.actions.convertoro")}
|
||||
</Button>
|
||||
<Button onClick={() => setOpen(false)}>
|
||||
<Button onClick={() => setVisible(false)}>
|
||||
{t("general.actions.close")}
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -390,9 +390,9 @@ export function ContractConvertToRo({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Popover content={popContent} open={open}>
|
||||
<Popover content={popContent} visible={visible}>
|
||||
<Button
|
||||
onClick={() => setOpen(true)}
|
||||
onClick={() => setVisible(true)}
|
||||
loading={loading}
|
||||
disabled={!contract.dailyrate || !contract.actualreturn || disabled}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WarningFilled } from "@ant-design/icons";
|
||||
import { Form, Input, InputNumber, Space } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
@@ -68,6 +68,30 @@ export default function ContractFormComponent({
|
||||
<FormDateTimePicker />
|
||||
</Form.Item>
|
||||
)}
|
||||
{create && (
|
||||
<Form.Item
|
||||
shouldUpdate={(p, c) => p.scheduledreturn !== c.scheduledreturn}
|
||||
>
|
||||
{() => {
|
||||
const insuranceOver =
|
||||
selectedCar &&
|
||||
selectedCar.insuranceexpires &&
|
||||
moment(selectedCar.insuranceexpires)
|
||||
.endOf("day")
|
||||
.isBefore(moment(form.getFieldValue("scheduledreturn")));
|
||||
if (insuranceOver)
|
||||
return (
|
||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||
<span>
|
||||
<WarningFilled style={{ marginRight: ".3rem" }} />
|
||||
{t("contracts.labels.insuranceexpired")}
|
||||
</span>
|
||||
</Space>
|
||||
);
|
||||
return <></>;
|
||||
}}
|
||||
</Form.Item>
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
@@ -90,16 +114,17 @@ export default function ContractFormComponent({
|
||||
>
|
||||
{() => {
|
||||
const mileageOver =
|
||||
selectedCar &&
|
||||
selectedCar.nextservicekm <= form.getFieldValue("kmstart");
|
||||
|
||||
selectedCar && selectedCar.nextservicekm
|
||||
? selectedCar.nextservicekm <= form.getFieldValue("kmstart")
|
||||
: false;
|
||||
const dueForService =
|
||||
selectedCar &&
|
||||
selectedCar.nextservicedate &&
|
||||
dayjs(selectedCar.nextservicedate).isBefore(
|
||||
dayjs(form.getFieldValue("scheduledreturn"))
|
||||
);
|
||||
|
||||
moment(selectedCar.nextservicedate)
|
||||
.endOf("day")
|
||||
.isSameOrBefore(
|
||||
moment(form.getFieldValue("scheduledreturn"))
|
||||
);
|
||||
if (mileageOver || dueForService)
|
||||
return (
|
||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||
@@ -117,7 +142,6 @@ export default function ContractFormComponent({
|
||||
</span>
|
||||
</Space>
|
||||
);
|
||||
|
||||
return <></>;
|
||||
}}
|
||||
</Form.Item>
|
||||
@@ -190,9 +214,9 @@ export default function ContractFormComponent({
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
const dlExpiresBeforeReturn = dayjs(
|
||||
const dlExpiresBeforeReturn = moment(
|
||||
form.getFieldValue("driver_dlexpiry")
|
||||
).isBefore(dayjs(form.getFieldValue("scheduledreturn")));
|
||||
).isBefore(moment(form.getFieldValue("scheduledreturn")));
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, Input, Modal, Typography } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import aamva from "../../utils/aamva";
|
||||
@@ -26,8 +26,8 @@ export default function ContractLicenseDecodeButton({ form }) {
|
||||
|
||||
const values = {
|
||||
driver_dlnumber: decodedBarcode.dl,
|
||||
driver_dlexpiry: dayjs(
|
||||
`20${decodedBarcode.expiration_date}${dayjs(
|
||||
driver_dlexpiry: moment(
|
||||
`20${decodedBarcode.expiration_date}${moment(
|
||||
decodedBarcode.birthday
|
||||
).format("DD")}`
|
||||
),
|
||||
@@ -38,7 +38,7 @@ export default function ContractLicenseDecodeButton({ form }) {
|
||||
driver_city: decodedBarcode.city,
|
||||
driver_state: decodedBarcode.state,
|
||||
driver_zip: decodedBarcode.postal_code,
|
||||
driver_dob: dayjs(decodedBarcode.birthday),
|
||||
driver_dob: moment(decodedBarcode.birthday),
|
||||
};
|
||||
|
||||
form.setFieldsValue(values);
|
||||
@@ -55,7 +55,7 @@ export default function ContractLicenseDecodeButton({ form }) {
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
open={modalVisible}
|
||||
visible={modalVisible}
|
||||
okText={t("contracts.actions.senddltoform")}
|
||||
onOk={handleInsertForm}
|
||||
okButtonProps={{ disabled: !!!decodedBarcode }}
|
||||
@@ -94,14 +94,14 @@ export default function ContractLicenseDecodeButton({ form }) {
|
||||
{decodedBarcode.address}
|
||||
</DataLabel>
|
||||
<DataLabel label={t("contracts.fields.driver_dlexpiry")}>
|
||||
{dayjs(
|
||||
`20${decodedBarcode.expiration_date}${dayjs(
|
||||
{moment(
|
||||
`20${decodedBarcode.expiration_date}${moment(
|
||||
decodedBarcode.birthday
|
||||
).format("DD")}`
|
||||
).format("MM/DD/YYYY")}
|
||||
</DataLabel>
|
||||
<DataLabel label={t("contracts.fields.driver_dob")}>
|
||||
{dayjs(decodedBarcode.birthday).format("MM/DD/YYYY")}
|
||||
{moment(decodedBarcode.birthday).format("MM/DD/YYYY")}
|
||||
</DataLabel>
|
||||
<div>
|
||||
<Typography.Title level={4}>
|
||||
|
||||
@@ -31,7 +31,7 @@ export function ContractsFindModalContainer({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { open } = contractFinderModal;
|
||||
const { visible } = contractFinderModal;
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@@ -52,14 +52,14 @@ export function ContractsFindModalContainer({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (visible) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [open, form]);
|
||||
}, [visible, form]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
visible={visible}
|
||||
width="70%"
|
||||
title={t("contracts.labels.findermodal")}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
|
||||
@@ -3,13 +3,13 @@ import { Button, Card, Input, Space, Table, Typography } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate, useLocation } from "react-router-dom";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container";
|
||||
|
||||
import dayjs from "../../utils/day";
|
||||
import moment from "moment";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
@@ -39,7 +39,7 @@ export function ContractsList({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" },
|
||||
});
|
||||
const history = useNavigate();
|
||||
const history = useHistory();
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const { page } = search;
|
||||
|
||||
@@ -152,8 +152,8 @@ export function ContractsList({
|
||||
render: (text, record) =>
|
||||
(record.actualreturn &&
|
||||
record.start &&
|
||||
`${dayjs(record.actualreturn)
|
||||
.diff(dayjs(record.start), "day", true)
|
||||
`${moment(record.actualreturn)
|
||||
.diff(moment(record.start), "days", true)
|
||||
.toFixed(1)} days`) ||
|
||||
"",
|
||||
},
|
||||
@@ -164,7 +164,7 @@ export function ContractsList({
|
||||
search.page = pagination.current;
|
||||
search.sortcolumn = sorter.columnKey;
|
||||
search.sortorder = sorter.order;
|
||||
history({ search: queryString.stringify(search) });
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -179,7 +179,7 @@ export function ContractsList({
|
||||
<Button
|
||||
onClick={() => {
|
||||
delete search.search;
|
||||
history({ search: queryString.stringify(search) });
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}}
|
||||
>
|
||||
{t("general.actions.clear")}
|
||||
@@ -196,7 +196,7 @@ export function ContractsList({
|
||||
placeholder={search.searh || t("general.labels.search")}
|
||||
onSearch={(value) => {
|
||||
search.search = value;
|
||||
history({ search: queryString.stringify(search) });
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { Dropdown } from "antd";
|
||||
import { Dropdown, Menu } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -18,16 +18,20 @@ export function ContractsRatesChangeButton({ disabled, form, bodyshop }) {
|
||||
form.setFieldsValue(rate);
|
||||
};
|
||||
|
||||
const menuItems = bodyshop.md_ccc_rates.map((i, idx) => ({
|
||||
key: idx,
|
||||
label: i.label,
|
||||
value: i,
|
||||
}));
|
||||
|
||||
const menu = {items: menuItems, onClick: handleClick};
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.md_ccc_rates.map((rate, idx) => (
|
||||
<Menu.Item value={rate} key={idx}>
|
||||
{rate.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown menu={menu} disabled={disabled}>
|
||||
<Dropdown overlay={menu} disabled={disabled}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Card, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
@@ -11,9 +11,9 @@ export default function CourtesyCarContractListComponent({
|
||||
contracts,
|
||||
totalContracts,
|
||||
}) {
|
||||
const search =queryString.parse(useLocation().search);
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const { page, sortcolumn, sortorder } = search;
|
||||
const history = useNavigate();
|
||||
const history = useHistory();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function CourtesyCarContractListComponent({
|
||||
search.page = pagination.current;
|
||||
search.sortcolumn = sorter.columnKey;
|
||||
search.sortorder = sorter.order;
|
||||
history({ search: queryString.stringify(search) });
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { WarningFilled } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, Form, Input, InputNumber, Space } from "antd";
|
||||
import {PageHeader} from "@ant-design/pro-layout";
|
||||
import dayjs from "../../utils/day";
|
||||
import { Button, Form, Input, InputNumber, PageHeader, Space } from "antd";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CHECK_CC_FLEET_NUMBER } from "../../graphql/courtesy-car.queries";
|
||||
@@ -264,7 +263,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
const nextservicedate = form.getFieldValue("nextservicedate");
|
||||
const dueForService =
|
||||
nextservicedate &&
|
||||
dayjs(nextservicedate).endOf("day").isSameOrBefore(dayjs());
|
||||
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
|
||||
|
||||
if (dueForService)
|
||||
return (
|
||||
@@ -305,7 +304,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
const expires = form.getFieldValue("registrationexpires");
|
||||
|
||||
const dateover =
|
||||
expires && dayjs(expires).endOf("day").isBefore(dayjs());
|
||||
expires && moment(expires).endOf("day").isBefore(moment());
|
||||
|
||||
if (dateover)
|
||||
return (
|
||||
@@ -341,7 +340,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
const expires = form.getFieldValue("insuranceexpires");
|
||||
|
||||
const dateover =
|
||||
expires && dayjs(expires).endOf("day").isBefore(dayjs());
|
||||
expires && moment(expires).endOf("day").isBefore(moment());
|
||||
|
||||
if (dateover)
|
||||
return (
|
||||
|
||||
@@ -7,7 +7,7 @@ import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectCourtesyCarReturn } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CourtesyCarReturnModalComponent from "./courtesy-car-return-modal.component";
|
||||
import dayjs from "../../utils/day";
|
||||
import moment from "moment";
|
||||
import { RETURN_CONTRACT } from "../../graphql/cccontracts.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
|
||||
@@ -26,7 +26,7 @@ export function CCReturnModalContainer({
|
||||
bodyshop,
|
||||
}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { open, context, actions } = courtesyCarReturnModal;
|
||||
const { visible, context, actions } = courtesyCarReturnModal;
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [updateContract] = useMutation(RETURN_CONTRACT);
|
||||
@@ -64,7 +64,7 @@ export function CCReturnModalContainer({
|
||||
return (
|
||||
<Modal
|
||||
title={t("courtesycars.labels.return")}
|
||||
open={open}
|
||||
visible={visible}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
width={"90%"}
|
||||
okText={t("general.actions.save")}
|
||||
@@ -74,7 +74,7 @@ export function CCReturnModalContainer({
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleFinish}
|
||||
initialValues={{ fuel: 100, actualreturn: dayjs(new Date()) }}
|
||||
initialValues={{ fuel: 100, actualreturn: moment(new Date()) }}
|
||||
>
|
||||
<CourtesyCarReturnModalComponent />
|
||||
</Form>
|
||||
|
||||
@@ -4,11 +4,12 @@ import {
|
||||
Card,
|
||||
Dropdown,
|
||||
Input,
|
||||
Menu,
|
||||
Space,
|
||||
Table,
|
||||
Tooltip,
|
||||
} from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -16,13 +17,18 @@ import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" },
|
||||
});
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [filter, setFilter] = useLocalStorage(
|
||||
"filter_courtesy_cars_list",
|
||||
null
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const columns = [
|
||||
@@ -49,6 +55,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||
filteredValue: filter?.status || null,
|
||||
filters: [
|
||||
{
|
||||
text: t("courtesycars.status.in"),
|
||||
@@ -71,18 +78,34 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
const { nextservicedate, nextservicekm, mileage } = record;
|
||||
const { nextservicedate, nextservicekm, mileage, insuranceexpires } =
|
||||
record;
|
||||
|
||||
const mileageOver = nextservicekm ? nextservicekm <= mileage : false;
|
||||
|
||||
const dueForService =
|
||||
nextservicedate && dayjs(nextservicedate).endOf('day').isSameOrBefore(dayjs());
|
||||
nextservicedate &&
|
||||
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
|
||||
|
||||
const insuranceOver =
|
||||
insuranceexpires &&
|
||||
moment(insuranceexpires).endOf("day").isBefore(moment());
|
||||
|
||||
return (
|
||||
<Space>
|
||||
{t(record.status)}
|
||||
{(mileageOver || dueForService) && (
|
||||
<Tooltip title={t("contracts.labels.cardueforservice")}>
|
||||
{(mileageOver || dueForService || insuranceOver) && (
|
||||
<Tooltip
|
||||
title={
|
||||
(mileageOver || dueForService) && insuranceOver
|
||||
? t("contracts.labels.insuranceexpired") +
|
||||
" / " +
|
||||
t("contracts.labels.cardueforservice")
|
||||
: insuranceOver
|
||||
? t("contracts.labels.insuranceexpired")
|
||||
: t("contracts.labels.cardueforservice")
|
||||
}
|
||||
>
|
||||
<WarningFilled style={{ color: "tomato" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
@@ -95,6 +118,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
dataIndex: "readiness",
|
||||
key: "readiness",
|
||||
sorter: (a, b) => alphaSort(a.readiness, b.readiness),
|
||||
filteredValue: filter?.readiness || null,
|
||||
filters: [
|
||||
{
|
||||
text: t("courtesycars.readiness.ready"),
|
||||
@@ -210,7 +234,8 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
setState({ ...state, sortedInfo: sorter });
|
||||
setFilter(filters);
|
||||
};
|
||||
|
||||
const tableData = searchText
|
||||
@@ -227,27 +252,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
: courtesycars;
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: "courtesycar_inventory",
|
||||
label: t("printcenter.courtesycarcontract.courtesy_car_inventory"),
|
||||
onClick: () =>
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("courtesycar").courtesy_car_inventory.key,
|
||||
variables: {
|
||||
//id: contract.id
|
||||
},
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const menu = { items };
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("menus.header.courtesycars")}
|
||||
@@ -256,7 +260,30 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Dropdown trigger="click" menu={menu}>
|
||||
<Dropdown
|
||||
trigger="click"
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() =>
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("courtesycar").courtesy_car_inventory
|
||||
.key,
|
||||
variables: {
|
||||
//id: contract.id
|
||||
},
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("printcenter.courtesycarcontract.courtesy_car_inventory")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<Button>{t("general.labels.print")}</Button>
|
||||
</Dropdown>
|
||||
<Link to={`/manage/courtesycars/new`}>
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { QUERY_CSI_RESPONSE_BY_PK } from "../../graphql/csi.queries";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import ConfigFormComponents from "../config-form-components/config-form-components.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
@@ -44,6 +45,13 @@ export default function CsiResponseFormContainer() {
|
||||
readOnly
|
||||
componentList={data.csi_by_pk.csiquestion.config}
|
||||
/>
|
||||
{data.csi_by_pk.validuntil ? (
|
||||
<>
|
||||
{t("csi.fields.validuntil")}
|
||||
{": "}
|
||||
<DateFormatter>{data.csi_by_pk.validuntil}</DateFormatter>
|
||||
</>
|
||||
) : null}
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -3,11 +3,13 @@ import { Button, Card, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate, useLocation } from "react-router-dom";
|
||||
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";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import OwnerNameDisplay, {
|
||||
OwnerNameDisplayFunction,
|
||||
} from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
export default function CsiResponseListPaginated({
|
||||
refetch,
|
||||
@@ -16,23 +18,23 @@ export default function CsiResponseListPaginated({
|
||||
total,
|
||||
}) {
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const { responseid, page, sortcolumn, sortorder } = search;
|
||||
const history = useNavigate();
|
||||
const { responseid } = search;
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" },
|
||||
page: "",
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
width: "8%",
|
||||
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
|
||||
sortOrder: sortcolumn === "ro_number" && sortorder,
|
||||
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link to={"/manage/jobs/" + record.job.id}>
|
||||
{record.job.ro_number || t("general.labels.na")}
|
||||
@@ -41,15 +43,18 @@ export default function CsiResponseListPaginated({
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
|
||||
width: "25%",
|
||||
sortOrder: sortcolumn === "owner" && sortorder,
|
||||
dataIndex: "owner_name",
|
||||
key: "owner_name",
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
OwnerNameDisplayFunction(a.job),
|
||||
OwnerNameDisplayFunction(b.job)
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner_name" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.job.owner ? (
|
||||
<Link to={"/manage/owners/" + record.job.owner.id}>
|
||||
return record.job.ownerid ? (
|
||||
<Link to={"/manage/owners/" + record.job.ownerid}>
|
||||
<OwnerNameDisplay ownerObject={record.job} />
|
||||
</Link>
|
||||
) : (
|
||||
@@ -64,9 +69,9 @@ export default function CsiResponseListPaginated({
|
||||
dataIndex: "completedon",
|
||||
key: "completedon",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => a.completedon - b.completedon,
|
||||
width: "25%",
|
||||
sortOrder: sortcolumn === "completedon" && sortorder,
|
||||
sorter: (a, b) => dateSort(a.completedon, b.completedon),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "completedon" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.completedon ? (
|
||||
<DateFormatter>{record.completedon}</DateFormatter>
|
||||
@@ -76,22 +81,23 @@ export default function CsiResponseListPaginated({
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
search.page = pagination.current;
|
||||
search.sortcolumn = sorter.columnKey;
|
||||
search.sortorder = sorter.order;
|
||||
history({ search: queryString.stringify(search) });
|
||||
setState({
|
||||
...state,
|
||||
filteredInfo: filters,
|
||||
sortedInfo: sorter,
|
||||
page: pagination.current,
|
||||
});
|
||||
};
|
||||
|
||||
const handleOnRowClick = (record) => {
|
||||
if (record) {
|
||||
if (record.id) {
|
||||
search.responseid = record.id;
|
||||
history({ search: queryString.stringify(search) });
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}
|
||||
} else {
|
||||
delete search.responseid;
|
||||
history({ search: queryString.stringify(search) });
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,7 +114,7 @@ export default function CsiResponseListPaginated({
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
current: parseInt(state.page || 1),
|
||||
total: total,
|
||||
}}
|
||||
columns={columns}
|
||||
@@ -122,13 +128,6 @@ export default function CsiResponseListPaginated({
|
||||
selectedRowKeys: [responseid],
|
||||
type: "radio",
|
||||
}}
|
||||
onRow={(record, rowIndex) => {
|
||||
return {
|
||||
onClick: (event) => {
|
||||
handleOnRowClick(record);
|
||||
}, // click row
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Card } from "antd";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../../utils/day";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
@@ -27,7 +27,7 @@ export default function DashboardMonthlyEmployeeEfficiency({
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const ticketsByDate = _.groupBy(data.monthly_employee_efficiency, (item) =>
|
||||
dayjs(item.date).format("YYYY-MM-DD")
|
||||
moment(item.date).format("YYYY-MM-DD")
|
||||
);
|
||||
|
||||
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
||||
@@ -53,7 +53,7 @@ export default function DashboardMonthlyEmployeeEfficiency({
|
||||
((dailyHrs.productive - dailyHrs.actual) / dailyHrs.actual + 1) * 100;
|
||||
|
||||
const theValue = {
|
||||
date: dayjs(val).format("DD"),
|
||||
date: moment(val).format("DD"),
|
||||
// ...dailyHrs,
|
||||
actual: dailyHrs.actual.toFixed(1),
|
||||
productive: dailyHrs.productive.toFixed(1),
|
||||
@@ -159,9 +159,9 @@ export default function DashboardMonthlyEmployeeEfficiency({
|
||||
}
|
||||
|
||||
export const DashboardMonthlyEmployeeEfficiencyGql = `
|
||||
monthly_employee_efficiency: timetickets(where: {_and: [{date: {_gte: "${dayjs()
|
||||
monthly_employee_efficiency: timetickets(where: {_and: [{date: {_gte: "${moment()
|
||||
.startOf("month")
|
||||
.format("YYYY-MM-DD")}"}},{date: {_lte: "${dayjs()
|
||||
.format("YYYY-MM-DD")}"}},{date: {_lte: "${moment()
|
||||
.endOf("month")
|
||||
.format("YYYY-MM-DD")}"}} ]}) {
|
||||
actualhrs
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Card } from "antd";
|
||||
import dayjs from "../../../utils/day";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import _ from "lodash";
|
||||
@@ -24,7 +24,7 @@ export default function DashboardMonthlyRevenueGraph({ data, ...cardProps }) {
|
||||
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const jobsByDate = _.groupBy(data.monthly_sales, (item) =>
|
||||
dayjs(item.date_invoiced).format("YYYY-MM-DD")
|
||||
moment(item.date_invoiced).format("YYYY-MM-DD")
|
||||
);
|
||||
|
||||
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
||||
@@ -43,7 +43,7 @@ export default function DashboardMonthlyRevenueGraph({ data, ...cardProps }) {
|
||||
}
|
||||
|
||||
const theValue = {
|
||||
date: dayjs(val).format("DD"),
|
||||
date: moment(val).format("DD"),
|
||||
dailySales: dailySales.getAmount() / 100,
|
||||
accSales:
|
||||
acc.length > 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Card, Statistic } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import dayjs from "../../../utils/day";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
@@ -36,10 +36,10 @@ export const DashboardProjectedMonthlySalesGql = `
|
||||
_or: [
|
||||
{_and: [
|
||||
{date_invoiced:{_is_null: false }},
|
||||
{date_invoiced: {_gte: "${dayjs()
|
||||
{date_invoiced: {_gte: "${moment()
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {date_invoiced: {_lte: "${dayjs()
|
||||
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
||||
.endOf("month")
|
||||
.endOf("day")
|
||||
.toISOString()}"}}]},
|
||||
@@ -47,10 +47,10 @@ export const DashboardProjectedMonthlySalesGql = `
|
||||
|
||||
_and:[
|
||||
{date_invoiced:{_is_null: true }},
|
||||
{actual_completion: {_gte: "${dayjs()
|
||||
{actual_completion: {_gte: "${moment()
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {actual_completion: {_lte: "${dayjs()
|
||||
.toISOString()}"}}, {actual_completion: {_lte: "${moment()
|
||||
.endOf("month")
|
||||
.endOf("day")
|
||||
.toISOString()}"}}
|
||||
@@ -61,10 +61,10 @@ _and:[
|
||||
{_and: [
|
||||
{date_invoiced: {_is_null: true}},
|
||||
{actual_completion: {_is_null: true}}
|
||||
{scheduled_completion: {_gte: "${dayjs()
|
||||
{scheduled_completion: {_gte: "${moment()
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {scheduled_completion: {_lte: "${dayjs()
|
||||
.toISOString()}"}}, {scheduled_completion: {_lte: "${moment()
|
||||
.endOf("month")
|
||||
.endOf("day")
|
||||
.toISOString()}"}}
|
||||
|
||||
@@ -3,21 +3,32 @@ import {
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Space, Table, Tooltip } from "antd";
|
||||
import dayjs from "../../../utils/day";
|
||||
import { Card, Space, Switch, Table, Tooltip, Typography } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { TimeFormatter } from "../../../utils/DateFormatter";
|
||||
import { onlyUnique } from "../../../utils/arrayHelper";
|
||||
import { alphaSort, dateSort } from "../../../utils/sorters";
|
||||
import useLocalStorage from "../../../utils/useLocalStorage";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
||||
import OwnerNameDisplay, {
|
||||
OwnerNameDisplayFunction,
|
||||
} from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import {pageLimit} from "../../../utils/config";
|
||||
|
||||
export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: {},
|
||||
});
|
||||
const [isTvModeScheduledIn, setIsTvModeScheduledIn] = useLocalStorage(
|
||||
"isTvModeScheduledIn",
|
||||
false
|
||||
);
|
||||
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_in_today)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
@@ -31,6 +42,12 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
alt_transport: item.job.alt_transport,
|
||||
clm_no: item.job.clm_no,
|
||||
jobid: item.job.jobid,
|
||||
joblines_body: item.job.joblines
|
||||
.filter((l) => l.mod_lbr_ty !== "LAR")
|
||||
.reduce((acc, val) => acc + val.mod_lb_hrs, 0),
|
||||
joblines_ref: item.job.joblines
|
||||
.filter((l) => l.mod_lbr_ty === "LAR")
|
||||
.reduce((acc, val) => acc + val.mod_lb_hrs, 0),
|
||||
ins_co_nm: item.job.ins_co_nm,
|
||||
iouparent: item.job.iouparent,
|
||||
ownerid: item.job.ownerid,
|
||||
@@ -49,21 +66,202 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
v_vin: item.job.v_vin,
|
||||
vehicleid: item.job.vehicleid,
|
||||
note: item.note,
|
||||
start: dayjs(item.start).format("hh:mm a"),
|
||||
start: item.start,
|
||||
title: item.title,
|
||||
};
|
||||
appt.push(i);
|
||||
}
|
||||
});
|
||||
appt.sort(function (a, b) {
|
||||
return new dayjs(a.start) - new dayjs(b.start);
|
||||
return new moment(a.start) - new moment(b.start);
|
||||
});
|
||||
|
||||
const columns = [
|
||||
const tvFontSize = 16;
|
||||
const tvFontWeight = "bold";
|
||||
|
||||
const tvColumns = [
|
||||
{
|
||||
title: t("appointments.fields.time"),
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => dateSort(a.start, b.start),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "start" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<TimeFormatter>{record.start}</TimeFormatter>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||
a.v_model_desc || ""
|
||||
}`,
|
||||
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{`${
|
||||
record.v_model_yr || ""
|
||||
} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "alt_transport" &&
|
||||
state.sortedInfo.order,
|
||||
filters:
|
||||
(appt &&
|
||||
appt
|
||||
.map((j) => j.alt_transport)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Alt. Transport",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.alt_transport}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.lab"),
|
||||
dataIndex: "joblines_body",
|
||||
key: "joblines_body",
|
||||
sorter: (a, b) => a.joblines_body - b.joblines_body,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "joblines_body" &&
|
||||
state.sortedInfo.order,
|
||||
align: "right",
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.joblines_body.toFixed(1)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.lar"),
|
||||
dataIndex: "joblines_ref",
|
||||
key: "joblines_ref",
|
||||
sorter: (a, b) => a.joblines_ref - b.joblines_ref,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order,
|
||||
align: "right",
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.joblines_ref.toFixed(1)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("appointments.fields.time"),
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => dateSort(a.start, b.start),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "start" && state.sortedInfo.order,
|
||||
render: (text, record) => <TimeFormatter>{record.start}</TimeFormatter>,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
@@ -91,7 +289,10 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) =>
|
||||
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
@@ -108,23 +309,16 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
title: t("dashboard.labels.phone"),
|
||||
dataIndex: "ownr_ph",
|
||||
key: "ownr_ph",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
<Space size="small" wrap>
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -134,7 +328,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
|
||||
<a href={`mailto:${record.ownr_ea}`}>{record.ownr_ea}</a>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -142,6 +336,15 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||
a.v_model_desc || ""
|
||||
}`,
|
||||
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
@@ -165,43 +368,80 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.time"),
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
|
||||
filters:
|
||||
(appt &&
|
||||
appt
|
||||
.map((j) => j.ins_co_nm)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Ins. Co.*",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "alt_transport" &&
|
||||
state.sortedInfo.order,
|
||||
filters:
|
||||
(appt &&
|
||||
appt
|
||||
.map((j) => j.alt_transport)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Alt. Transport",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (sorter) => {
|
||||
setState({ ...state, sortedInfo: sorter });
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.scheduledintoday", {
|
||||
date: dayjs().startOf("day").format("MM/DD/YYYY"),
|
||||
title={t("dashboard.titles.scheduledindate", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
})}
|
||||
extra={
|
||||
<Space>
|
||||
<Typography.Text>{t("general.labels.tvmode")}</Typography.Text>
|
||||
<Switch
|
||||
onClick={() => setIsTvModeScheduledIn(!isTvModeScheduledIn)}
|
||||
defaultChecked={isTvModeScheduledIn}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
{...cardProps}
|
||||
>
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
columns={isTvModeScheduledIn ? tvColumns : columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
style={{ height: "85%" }}
|
||||
dataSource={appt}
|
||||
size={isTvModeScheduledIn ? "small" : "middle"}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -209,9 +449,9 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
}
|
||||
|
||||
export const DashboardScheduledInTodayGql = `
|
||||
scheduled_in_today: appointments(where: {start: {_gte: "${dayjs()
|
||||
scheduled_in_today: appointments(where: {start: {_gte: "${moment()
|
||||
.startOf("day")
|
||||
.toISOString()}", _lte: "${dayjs()
|
||||
.toISOString()}", _lte: "${moment()
|
||||
.endOf("day")
|
||||
.toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) {
|
||||
canceled
|
||||
@@ -220,6 +460,10 @@ export const DashboardScheduledInTodayGql = `
|
||||
alt_transport
|
||||
clm_no
|
||||
jobid: id
|
||||
joblines(where: {removed: {_eq: false}}) {
|
||||
mod_lb_hrs
|
||||
mod_lbr_ty
|
||||
}
|
||||
ins_co_nm
|
||||
iouparent
|
||||
ownerid
|
||||
|
||||
@@ -3,38 +3,272 @@ import {
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Space, Table, Tooltip } from "antd";
|
||||
import dayjs from "../../../utils/day";
|
||||
import { Card, Space, Switch, Table, Tooltip, Typography } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { TimeFormatter } from "../../../utils/DateFormatter";
|
||||
import { onlyUnique } from "../../../utils/arrayHelper";
|
||||
import { alphaSort, dateSort } from "../../../utils/sorters";
|
||||
import useLocalStorage from "../../../utils/useLocalStorage";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
||||
import OwnerNameDisplay, {
|
||||
OwnerNameDisplayFunction,
|
||||
} from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import {pageLimit} from "../../../utils/config";
|
||||
|
||||
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
filteredInfo: {},
|
||||
});
|
||||
const [isTvModeScheduledOut, setIsTvModeScheduledOut] = useLocalStorage(
|
||||
"isTvModeScheduledOut",
|
||||
false
|
||||
);
|
||||
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_out_today)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const filteredScheduledOutToday = data.scheduled_out_today.map((item) => {
|
||||
return {
|
||||
...item,
|
||||
scheduled_completion: dayjs(item.scheduled_completion).format("hh:mm a"),
|
||||
timestamp: dayjs(item.scheduled_completion).valueOf(),
|
||||
}
|
||||
}).sort((a, b) => a.timestamp - b.timestamp);
|
||||
data.scheduled_out_today.forEach((item) => {
|
||||
item.joblines_body = item.joblines
|
||||
? item.joblines
|
||||
.filter((l) => l.mod_lbr_ty !== "LAR")
|
||||
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
|
||||
: 0;
|
||||
item.joblines_ref = item.joblines
|
||||
? item.joblines
|
||||
.filter((l) => l.mod_lbr_ty === "LAR")
|
||||
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
|
||||
: 0;
|
||||
});
|
||||
data.scheduled_out_today.sort(function (a, b) {
|
||||
return new Date(a.scheduled_completion) - new Date(b.scheduled_completion);
|
||||
});
|
||||
|
||||
const columns = [
|
||||
const tvFontSize = 18;
|
||||
const tvFontWeight = "bold";
|
||||
|
||||
const tvColumns = [
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
dataIndex: "scheduled_completion",
|
||||
key: "scheduled_completion",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
dateSort(a.scheduled_completion, b.scheduled_completion),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "scheduled_completion" &&
|
||||
state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||
a.v_model_desc || ""
|
||||
}`,
|
||||
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</span>
|
||||
</Link>
|
||||
) : (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{`${
|
||||
record.v_model_yr || ""
|
||||
} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "alt_transport" &&
|
||||
state.sortedInfo.order,
|
||||
filters:
|
||||
(data.scheduled_out_today &&
|
||||
data.scheduled_out_today
|
||||
.map((j) => j.alt_transport)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Alt. Transport*",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.alt_transport}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
filters:
|
||||
(data.scheduled_out_today &&
|
||||
data.scheduled_out_today
|
||||
.map((j) => j.status)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Status*",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.status}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.lab"),
|
||||
dataIndex: "joblines_body",
|
||||
key: "joblines_body",
|
||||
sorter: (a, b) => a.joblines_body - b.joblines_body,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "joblines_body" &&
|
||||
state.sortedInfo.order,
|
||||
align: "right",
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.joblines_body.toFixed(1)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.lar"),
|
||||
dataIndex: "joblines_ref",
|
||||
key: "joblines_ref",
|
||||
sorter: (a, b) => a.joblines_ref - b.joblines_ref,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order,
|
||||
align: "right",
|
||||
render: (text, record) => (
|
||||
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
|
||||
{record.joblines_ref.toFixed(1)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
dataIndex: "scheduled_completion",
|
||||
key: "scheduled_completion",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
dateSort(a.scheduled_completion, b.scheduled_completion),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "scheduled_completion" &&
|
||||
state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
@@ -62,7 +296,10 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) =>
|
||||
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
@@ -79,23 +316,16 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
title: t("dashboard.labels.phone"),
|
||||
dataIndex: "ownr_ph",
|
||||
key: "ownr_ph",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
<Space size="small" wrap>
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -105,7 +335,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
|
||||
<a href={`mailto:${record.ownr_ea}`}>{record.ownr_ea}</a>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -113,6 +343,15 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
|
||||
a.v_model_desc || ""
|
||||
}`,
|
||||
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
|
||||
),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
@@ -136,43 +375,80 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
dataIndex: "scheduled_completion",
|
||||
key: "scheduled_completion",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
|
||||
filters:
|
||||
(data.scheduled_out_today &&
|
||||
data.scheduled_out_today
|
||||
.map((j) => j.ins_co_nm)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Ins. Co.*",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.ins_co_nm),
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "alt_transport" &&
|
||||
state.sortedInfo.order,
|
||||
filters:
|
||||
(data.scheduled_out_today &&
|
||||
data.scheduled_out_today
|
||||
.map((j) => j.alt_transport)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Alt. Transport*",
|
||||
value: [s],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => alphaSort(a.text, b.text))) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.alt_transport),
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (sorter) => {
|
||||
setState({ ...state, sortedInfo: sorter });
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.scheduledouttoday", {
|
||||
date: dayjs().startOf("day").format("MM/DD/YYYY"),
|
||||
title={t("dashboard.titles.scheduledoutdate", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
})}
|
||||
extra={
|
||||
<Space>
|
||||
<Typography.Text>{t("general.labels.tvmode")}</Typography.Text>
|
||||
<Switch
|
||||
onClick={() => setIsTvModeScheduledOut(!isTvModeScheduledOut)}
|
||||
defaultChecked={isTvModeScheduledOut}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
{...cardProps}
|
||||
>
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
columns={isTvModeScheduledOut ? tvColumns : columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
style={{ height: "85%" }}
|
||||
dataSource={filteredScheduledOutToday}
|
||||
dataSource={data.scheduled_out_today}
|
||||
size={isTvModeScheduledOut ? "small" : "middle"}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -184,11 +460,15 @@ export const DashboardScheduledOutTodayGql = `
|
||||
date_invoiced: {_is_null: true},
|
||||
ro_number: {_is_null: false},
|
||||
voided: {_eq: false},
|
||||
scheduled_completion: {_gte: "${dayjs().startOf("day").toISOString()}",
|
||||
_lte: "${dayjs().endOf("day").toISOString()}"}}) {
|
||||
scheduled_completion: {_gte: "${moment().startOf("day").toISOString()}",
|
||||
_lte: "${moment().endOf("day").toISOString()}"}}) {
|
||||
alt_transport
|
||||
clm_no
|
||||
jobid: id
|
||||
joblines(where: {removed: {_eq: false}}) {
|
||||
mod_lb_hrs
|
||||
mod_lbr_ty
|
||||
}
|
||||
ins_co_nm
|
||||
iouparent
|
||||
ownerid
|
||||
@@ -201,6 +481,7 @@ export const DashboardScheduledOutTodayGql = `
|
||||
production_vars
|
||||
ro_number
|
||||
scheduled_completion
|
||||
status
|
||||
suspended
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import Icon, { SyncOutlined } from "@ant-design/icons";
|
||||
import { gql, useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Dropdown, Space, notification } from "antd";
|
||||
import {PageHeader} from "@ant-design/pro-layout";
|
||||
import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd";
|
||||
import i18next from "i18next";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { Responsive, WidthProvider } from "react-grid-layout";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -123,15 +122,19 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
|
||||
[data]
|
||||
);
|
||||
const existingLayoutKeys = state.items.map((i) => i.i);
|
||||
|
||||
const menuItems = Object.keys(componentList).map((key) => ({
|
||||
key: key,
|
||||
label: componentList[key].label,
|
||||
value: key,
|
||||
disabled: existingLayoutKeys.includes(key),
|
||||
}));
|
||||
|
||||
const menu = {items: menuItems, onClick: handleAddComponent};
|
||||
const addComponentOverlay = (
|
||||
<Menu onClick={handleAddComponent}>
|
||||
{Object.keys(componentList).map((key) => (
|
||||
<Menu.Item
|
||||
key={key}
|
||||
value={key}
|
||||
disabled={existingLayoutKeys.includes(key)}
|
||||
>
|
||||
{componentList[key].label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
@@ -143,7 +146,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Dropdown menu={menu} trigger={["click"]}>
|
||||
<Dropdown overlay={addComponentOverlay} trigger={["click"]}>
|
||||
<Button>{t("dashboard.actions.addcomponent")}</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
@@ -272,26 +275,22 @@ const componentList = {
|
||||
h: 2,
|
||||
},
|
||||
ScheduleInToday: {
|
||||
label: i18next.t("dashboard.titles.scheduledintoday", {
|
||||
date: dayjs().startOf("day").format("MM/DD/YYYY"),
|
||||
}),
|
||||
label: i18next.t("dashboard.titles.scheduledintoday"),
|
||||
component: DashboardScheduledInToday,
|
||||
gqlFragment: DashboardScheduledInTodayGql,
|
||||
minW: 10,
|
||||
minW: 6,
|
||||
minH: 2,
|
||||
w: 10,
|
||||
h: 2,
|
||||
h: 3,
|
||||
},
|
||||
ScheduleOutToday: {
|
||||
label: i18next.t("dashboard.titles.scheduledouttoday", {
|
||||
date: dayjs().startOf("day").format("MM/DD/YYYY"),
|
||||
}),
|
||||
label: i18next.t("dashboard.titles.scheduledouttoday"),
|
||||
component: DashboardScheduledOutToday,
|
||||
gqlFragment: DashboardScheduledOutTodayGql,
|
||||
minW: 10,
|
||||
minW: 6,
|
||||
minH: 2,
|
||||
w: 10,
|
||||
h: 2,
|
||||
h: 3,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -303,22 +302,21 @@ const createDashboardQuery = (state) => {
|
||||
.map((item, index) => componentList[item.i].gqlFragment || "")
|
||||
.join("");
|
||||
return gql`
|
||||
query QUERY_DASHBOARD_DETAILS {
|
||||
${componentBasedAdditions || ""}
|
||||
query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""}
|
||||
monthly_sales: jobs(where: {_and: [
|
||||
{ voided: {_eq: false}},
|
||||
{date_invoiced: {_gte: "${dayjs()
|
||||
{date_invoiced: {_gte: "${moment()
|
||||
.startOf("month")
|
||||
.startOf("day")
|
||||
.toISOString()}"}}, {date_invoiced: {_lte: "${dayjs()
|
||||
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
||||
.endOf("month")
|
||||
.endOf("day")
|
||||
.toISOString()}"}}]}) {
|
||||
id
|
||||
ro_number
|
||||
date_invoiced
|
||||
job_totals
|
||||
rate_la1
|
||||
id
|
||||
ro_number
|
||||
date_invoiced
|
||||
job_totals
|
||||
rate_la1
|
||||
rate_la2
|
||||
rate_la3
|
||||
rate_la4
|
||||
@@ -341,43 +339,42 @@ const createDashboardQuery = (state) => {
|
||||
rate_mapa
|
||||
rate_mash
|
||||
rate_matd
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
id
|
||||
mod_lbr_ty
|
||||
mod_lb_hrs
|
||||
act_price
|
||||
part_qty
|
||||
part_type
|
||||
}
|
||||
}
|
||||
}
|
||||
production_jobs: jobs(where: { inproduction: { _eq: true } }) {
|
||||
production_jobs: jobs(where: { inproduction: { _eq: true } }) {
|
||||
id
|
||||
ro_number
|
||||
ins_co_nm
|
||||
job_totals
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
id
|
||||
ro_number
|
||||
ins_co_nm
|
||||
job_totals
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
id
|
||||
mod_lbr_ty
|
||||
mod_lb_hrs
|
||||
act_price
|
||||
part_qty
|
||||
part_type
|
||||
}
|
||||
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
mod_lbr_ty
|
||||
mod_lb_hrs
|
||||
act_price
|
||||
part_qty
|
||||
part_type
|
||||
}
|
||||
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}`;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user