Compare commits
346 Commits
feature/IO
...
vite
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0595d58154 | ||
|
|
e0855d370b | ||
|
|
6e8d55cad9 | ||
|
|
4e03756e6e | ||
|
|
edc2a0f122 | ||
|
|
419ae32d32 | ||
|
|
530032b5ea | ||
|
|
3ca48350a6 | ||
|
|
c8979091fd | ||
|
|
7358dee3e0 | ||
|
|
ff151444d2 | ||
|
|
000a2e471e | ||
|
|
94bcfa2afc | ||
|
|
bcb319d571 | ||
|
|
ca246fa6fa | ||
|
|
2dcfa87fa6 | ||
|
|
7daf7540b3 | ||
|
|
ce6940629d | ||
|
|
b706b96d32 | ||
|
|
2427bc72f2 | ||
|
|
8bc1a9d9ee | ||
|
|
5f8c878bec | ||
|
|
fb47b45eb6 | ||
|
|
ba30225ba1 | ||
|
|
cb4a6e8774 | ||
|
|
23f640028d | ||
|
|
dc1c492c0b | ||
|
|
bdff9f62c0 | ||
|
|
3e5e6263fe | ||
|
|
48cef3e188 | ||
|
|
76b25a716c | ||
|
|
86c3806e28 | ||
|
|
8c0d6b2f6b | ||
|
|
22ee8acd0d | ||
|
|
548cfefc41 | ||
|
|
ae98566bbd | ||
|
|
afdcfb7bf6 | ||
|
|
c1f6d06128 | ||
|
|
120a8a4576 | ||
|
|
89224e871c | ||
|
|
fa0d472fb6 | ||
|
|
29d5465afc | ||
|
|
b0f4ad7e4f | ||
|
|
7503d86c69 | ||
|
|
efd1c17033 | ||
|
|
c7a0072f2d | ||
|
|
908942ec09 | ||
|
|
e4d3b53349 | ||
|
|
2b0ecbdd91 | ||
|
|
461bc726aa | ||
|
|
80483b617b | ||
|
|
03ce5458b5 | ||
|
|
f9528c5ff7 | ||
|
|
61c03ee206 | ||
|
|
0e4f5b8b2a | ||
|
|
b5d4944ad8 | ||
|
|
50f84d40e1 | ||
|
|
16b0381007 | ||
|
|
a394d6b37e | ||
|
|
f8408908b2 | ||
|
|
eb8519dc1d | ||
|
|
36dd97394f | ||
|
|
03d4e4dcd1 | ||
|
|
6489a8666f | ||
|
|
da5739be8f | ||
|
|
5ea64ed805 | ||
|
|
397ae72626 | ||
|
|
9668a01415 | ||
|
|
6d7f49a858 | ||
|
|
f138ab82fb | ||
|
|
56256be106 | ||
|
|
8dfe98d9d7 | ||
|
|
9e127d1c71 | ||
|
|
d740446ccb | ||
|
|
cdec9840bf | ||
|
|
d0a2bb7da0 | ||
|
|
5de4ef5d83 | ||
|
|
f59bdf9030 | ||
|
|
cfe0727447 | ||
|
|
09d112350a | ||
|
|
52f8eabd2b | ||
|
|
a162b275a3 | ||
|
|
2e7232bb65 | ||
|
|
82dc9e1c56 | ||
|
|
272a3f579a | ||
|
|
ff1ceb20cb | ||
|
|
343179d4fe | ||
|
|
eabbc2211b | ||
|
|
7f587680ca | ||
|
|
9deab7d5d5 | ||
|
|
d61ed03ef1 | ||
|
|
e46be6c12b | ||
|
|
166efdc877 | ||
|
|
e1c325c11d | ||
|
|
f02aa36b8d | ||
|
|
d7faf11e27 | ||
|
|
c72d474dc7 | ||
|
|
73058c4405 | ||
|
|
72e8aba546 | ||
|
|
039f1e6a91 | ||
|
|
ddea46ddef | ||
|
|
2ada4ac44b | ||
|
|
6937f33134 | ||
|
|
9274742520 | ||
|
|
9b4c85c9e3 | ||
|
|
66c64ce9e0 | ||
|
|
c9cbffdec8 | ||
|
|
ca3145ce0f | ||
|
|
06a8d4257a | ||
|
|
b478d26c4c | ||
|
|
44a13ca0d5 | ||
|
|
52f9106776 | ||
|
|
1a2cc5623d | ||
|
|
d06037df1f | ||
|
|
0665bade1b | ||
|
|
51483f62e1 | ||
|
|
cb8632641e | ||
|
|
48a08819f3 | ||
|
|
8dc41519ce | ||
|
|
e255f0a664 | ||
|
|
adbfcddd9d | ||
|
|
e3aea55e91 | ||
|
|
bf6b1c202f | ||
|
|
0cab47f984 | ||
|
|
829e611692 | ||
|
|
e0e62a52be | ||
|
|
23c0f8e383 | ||
|
|
7da5d1a4fd | ||
|
|
43621bc6a2 | ||
|
|
909f6e8eb5 | ||
|
|
1e78106224 | ||
|
|
bb872a2b18 | ||
|
|
3fb3773744 | ||
|
|
7a708e32e4 | ||
|
|
d1cd0ea126 | ||
|
|
ee23d83858 | ||
|
|
764a89e5bf | ||
|
|
f4908ed265 | ||
|
|
61d6fdee95 | ||
|
|
ae9bff7e75 | ||
|
|
fcd9c19f0b | ||
|
|
430823dde0 | ||
|
|
7e919a7221 | ||
|
|
8031b4f91a | ||
|
|
d7cb8d3753 | ||
|
|
eb3560bd34 | ||
|
|
2fc82aad62 | ||
|
|
4f93b5af13 | ||
|
|
71663d64e4 | ||
|
|
67e475ce21 | ||
|
|
86b84e75cb | ||
|
|
8de4ebd30a | ||
|
|
7e7f055c34 | ||
|
|
6e04ed7c83 | ||
|
|
5ec07f9620 | ||
|
|
d3da4a8a1e | ||
|
|
9ee10dc5f8 | ||
|
|
94dd7c6f69 | ||
|
|
bbbb7867a2 | ||
|
|
572963d987 | ||
|
|
1cb4b228ce | ||
|
|
eb96f6467f | ||
|
|
2699b80e1a | ||
|
|
c72b4a25cf | ||
|
|
4a9684ba87 | ||
|
|
636be8989e | ||
|
|
dea7fd71ef | ||
|
|
1a93e1de41 | ||
|
|
69791a3cdd | ||
|
|
c2d6c980ed | ||
|
|
be08ed8551 | ||
|
|
65ce588287 | ||
|
|
78a136e277 | ||
|
|
0cc367b25e | ||
|
|
2ce8549502 | ||
|
|
690e65df0b | ||
|
|
a38611a584 | ||
|
|
b6b445dc21 | ||
|
|
7245d4eab2 | ||
|
|
0d0f24802f | ||
|
|
cf9b03d073 | ||
|
|
7b61c24461 | ||
|
|
260607cb72 | ||
|
|
810738539b | ||
|
|
80539949fb | ||
|
|
ebe5c5b113 | ||
|
|
525182c2a7 | ||
|
|
3704c0cb12 | ||
|
|
c8c844cfba | ||
|
|
4c4e16b0c9 | ||
|
|
f3db5d83c7 | ||
|
|
63ae37e5a9 | ||
|
|
69f727c4e5 | ||
|
|
76bcfce312 | ||
|
|
04cff4acb1 | ||
|
|
4e4fcc3ae4 | ||
|
|
485f9d6025 | ||
|
|
a697ade93a | ||
|
|
7db07b5a94 | ||
|
|
9ec50875a2 | ||
|
|
02b6875eec | ||
|
|
34c332e077 | ||
|
|
d360bcbb71 | ||
|
|
ba32a71786 | ||
|
|
7d6e61043e | ||
|
|
e7e4c534bc | ||
|
|
9fc586434e | ||
|
|
e438fa1d99 | ||
|
|
4abbf50a46 | ||
|
|
ef0bc8c313 | ||
|
|
aa8a719154 | ||
|
|
5aa3612e52 | ||
|
|
7d16ae5194 | ||
|
|
663dfe0441 | ||
|
|
49131ba68b | ||
|
|
c2dbdbd6cf | ||
|
|
0635c651a2 | ||
|
|
05667dd322 | ||
|
|
d3654ec16e | ||
|
|
ab299619dd | ||
|
|
e9d1f3af67 | ||
|
|
46b58a6e1b | ||
|
|
3e9279d89a | ||
|
|
1305277c09 | ||
|
|
3dcc1fe7e0 | ||
|
|
c6012f7335 | ||
|
|
0c7b5087f1 | ||
|
|
7589f78fe1 | ||
|
|
2e589c44a6 | ||
|
|
0074c73c2a | ||
|
|
3c47c672d4 | ||
|
|
213a02d5f2 | ||
|
|
258d99cd41 | ||
|
|
83356fa4ef | ||
|
|
25429e78f8 | ||
|
|
6e377f98a7 | ||
|
|
3a3b3af13f | ||
|
|
de90bd1bb0 | ||
|
|
aa6cb4c1d2 | ||
|
|
e871ba600f | ||
|
|
fe3698980d | ||
|
|
1f14688199 | ||
|
|
89b640f71c | ||
|
|
d13a9cd04a | ||
|
|
c0dab92d0e | ||
|
|
bc50f5e983 | ||
|
|
9c897972ad | ||
|
|
307e244475 | ||
|
|
33a1ac9be4 | ||
|
|
2c8d1accea | ||
|
|
351d6f274b | ||
|
|
e43bfe0d3a | ||
|
|
61ed0087e7 | ||
|
|
9d3aca646b | ||
|
|
e6e61466df | ||
|
|
db7f9fe2ab | ||
|
|
ded798fdf1 | ||
|
|
bfe94e3068 | ||
|
|
823f07409a | ||
|
|
1a4bc720c2 | ||
|
|
73cacdec24 | ||
|
|
17791ac971 | ||
|
|
a0cb30f986 | ||
|
|
07b46ed92b | ||
|
|
79dce5d069 | ||
|
|
e5d8cc2bea | ||
|
|
7b83430c02 | ||
|
|
ed0da08326 | ||
|
|
18998c4dbe | ||
|
|
e236d6e912 | ||
|
|
523df670df | ||
|
|
ce58181fc3 | ||
|
|
bed0669f73 | ||
|
|
b0d077e104 | ||
|
|
bdb2951330 | ||
|
|
87a01208fb | ||
|
|
2daee84fbf | ||
|
|
cdbf58f3ac | ||
|
|
f6a59bdf55 | ||
|
|
e12edd977e | ||
|
|
ea72d44b42 | ||
|
|
b925c991eb | ||
|
|
1e7f43fe3d | ||
|
|
c8460f6092 | ||
|
|
d2e4b7d9ec | ||
|
|
d1b9b5546b | ||
|
|
6f21de1901 | ||
|
|
25e8eaa1d4 | ||
|
|
1c5f74e4f0 | ||
|
|
84f0affaed | ||
|
|
46d514ad1c | ||
|
|
6fe736ce06 | ||
|
|
482b03c2d1 | ||
|
|
ce3c72fc47 | ||
|
|
1ff5ed4141 | ||
|
|
fc4b5c6b1d | ||
|
|
10e3421572 | ||
|
|
0117237988 | ||
|
|
2c232a71d5 | ||
|
|
373fd817d0 | ||
|
|
0ef2d9646d | ||
|
|
c8ac417200 | ||
|
|
661bedbe5b | ||
|
|
0514fbe89d | ||
|
|
87391ff06a | ||
|
|
b2c8e45d5e | ||
|
|
1261e8001b | ||
|
|
83e4fb3dc4 | ||
|
|
2dd56590d3 | ||
|
|
a67fb3576c | ||
|
|
65157a094f | ||
|
|
25173b0903 | ||
|
|
98b760251c | ||
|
|
b97de32a44 | ||
|
|
64f56d20dd | ||
|
|
5b3c547316 | ||
|
|
16d040daf9 | ||
|
|
a22c4bdf8c | ||
|
|
9cb2a4a021 | ||
|
|
95c9978ee7 | ||
|
|
fe80256a40 | ||
|
|
b0d1a7b65e | ||
|
|
ad79344709 | ||
|
|
5c164f807d | ||
|
|
a043f7be24 | ||
|
|
8d9611333c | ||
|
|
02bb2c06eb | ||
|
|
008bcaf41b | ||
|
|
a9dbfbd231 | ||
|
|
1df870ee4e | ||
|
|
517a087186 | ||
|
|
51747c554e | ||
|
|
92c8b54f85 | ||
|
|
f8e1758788 | ||
|
|
5c95c72f40 | ||
|
|
98f816b069 | ||
|
|
3ca6308dd2 | ||
|
|
a2c2aa11ac | ||
|
|
b5b772d0c2 | ||
|
|
4d8a2e635c | ||
|
|
0852d55837 | ||
|
|
4c38ddf3cd | ||
|
|
e15edeadb5 | ||
|
|
422c7baada | ||
|
|
2a2f8e51b3 | ||
|
|
85b1875a22 |
@@ -2,9 +2,8 @@ version: 2.1
|
|||||||
orbs:
|
orbs:
|
||||||
#snyk: snyk/snyk@0.0.8
|
#snyk: snyk/snyk@0.0.8
|
||||||
#cypress: cypress-io/cypress@1.23.0
|
#cypress: cypress-io/cypress@1.23.0
|
||||||
aws-s3: circleci/aws-s3@2.0.0
|
aws-s3: circleci/aws-s3@4.0.0
|
||||||
eb: circleci/aws-elastic-beanstalk@1.0.2
|
eb: circleci/aws-elastic-beanstalk@2.0.1
|
||||||
jira: circleci/jira@1.3.1
|
|
||||||
jobs:
|
jobs:
|
||||||
api-deploy:
|
api-deploy:
|
||||||
docker:
|
docker:
|
||||||
@@ -18,7 +17,6 @@ jobs:
|
|||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
- jira/notify
|
|
||||||
|
|
||||||
hasura-migrate:
|
hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
@@ -48,140 +46,36 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build
|
- run: npm run build
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-production/"
|
to: "s3://imex-online-production/"
|
||||||
- jira/notify
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
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:
|
app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:16.15.0
|
- image: cimg/node:18.18.2
|
||||||
parameters:
|
resource_class: xlarge
|
||||||
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
|
|
||||||
|
|
||||||
working_directory: ~/repo/client
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build
|
- run: npm run build
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://rome-online-production/"
|
to: "s3://imex-online-beta/"
|
||||||
- jira/notify
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
- run: yarn run build:test
|
|
||||||
|
|
||||||
- aws-s3/sync:
|
|
||||||
from: build
|
|
||||||
to: "s3://rome-online-test/"
|
|
||||||
- jira/notify
|
|
||||||
|
|
||||||
|
|
||||||
test-hasura-migrate:
|
test-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
@@ -212,26 +106,37 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- checkout:
|
- checkout:
|
||||||
path: ~/repo
|
path: ~/repo
|
||||||
|
|
||||||
- restore_cache:
|
|
||||||
name: Restore Yarn Package Cache
|
|
||||||
keys:
|
|
||||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
- run:
|
- run:
|
||||||
name: Install Dependencies
|
name: Install Dependencies
|
||||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
command: npm i
|
||||||
- save_cache:
|
|
||||||
name: Save Yarn Package Cache
|
|
||||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/yarn
|
|
||||||
|
|
||||||
- run: yarn run build:test
|
- run: npm run build:test
|
||||||
|
|
||||||
- aws-s3/sync:
|
- aws-s3/sync:
|
||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-test/"
|
to: "s3://imex-online-test/"
|
||||||
- jira/notify
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
|
test-app-beta-build:
|
||||||
|
docker:
|
||||||
|
- image: cimg/node:18.18.2
|
||||||
|
resource_class: snaptsoft/pfic
|
||||||
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
path: ~/repo
|
||||||
|
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: npm i
|
||||||
|
|
||||||
|
- run: npm run build:test
|
||||||
|
|
||||||
|
- aws-s3/sync:
|
||||||
|
from: build
|
||||||
|
to: "s3://imex-online-test-beta/"
|
||||||
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
admin-app-build:
|
admin-app-build:
|
||||||
docker:
|
docker:
|
||||||
@@ -274,42 +179,28 @@ workflows:
|
|||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: master
|
only: master
|
||||||
|
- app-beta-build:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: master-beta
|
||||||
- hasura-migrate:
|
- hasura-migrate:
|
||||||
secret: ${HASURA_PROD_SECRET}
|
secret: ${HASURA_PROD_SECRET}
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: master
|
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:
|
- test-app-build:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: test
|
only: test
|
||||||
|
- test-app-beta-build:
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only: test-beta
|
||||||
- test-hasura-migrate:
|
- test-hasura-migrate:
|
||||||
secret: ${HASURA_TEST_SECRET}
|
secret: ${HASURA_TEST_SECRET}
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: test
|
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:
|
#- admin-app-build:
|
||||||
#filters:
|
#filters:
|
||||||
#branches:
|
#branches:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,13 @@
|
|||||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||||
REACT_APP_GA_CODE=231099835
|
VITE_APP_GA_CODE=231099835
|
||||||
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"}
|
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||||
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||||
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
||||||
REACT_APP_CLOUDINARY_API_KEY=957865933348715
|
VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||||
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||||
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
REACT_APP_COUNTRY=USA
|
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
|
GENERATE_SOURCEMAP=true
|
||||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
|
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
|
||||||
|
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
|
||||||
REACT_APP_GA_CODE=231103507
|
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_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_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
|
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
|
||||||
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
|
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
|
||||||
REACT_APP_CLOUDINARY_API_KEY=473322739956866
|
REACT_APP_CLOUDINARY_API_KEY=473322739956866
|
||||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
|
||||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
REACT_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
|
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
|
||||||
REACT_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
REACT_APP_REPORTS_SERVER_URL=https://reports.imex.online
|
||||||
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql
|
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql
|
||||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql
|
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql
|
||||||
REACT_APP_GA_CODE=231103507
|
REACT_APP_GA_CODE=231099835
|
||||||
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_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_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
|
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
|
||||||
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
|
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
|
||||||
REACT_APP_CLOUDINARY_API_KEY=473322739956866
|
REACT_APP_CLOUDINARY_API_KEY=473322739956866
|
||||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
|
||||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
REACT_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/
|
REACT_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
|
||||||
REACT_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||||
REACT_APP_IS_TEST=true
|
REACT_APP_IS_TEST=true
|
||||||
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
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
client/.npmrc
Normal file
1
client/.npmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
legacy-peer-deps=true
|
||||||
Binary file not shown.
@@ -1,73 +1,68 @@
|
|||||||
// craco.config.js
|
// craco.config.js
|
||||||
const TerserPlugin = require("terser-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
const CracoLessPlugin = require("craco-less");
|
const CracoLessPlugin = require("craco-less");
|
||||||
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
const {convertLegacyToken} = require('@ant-design/compatible/lib');
|
||||||
|
const {theme} = require('antd/lib');
|
||||||
|
|
||||||
|
const {defaultAlgorithm, defaultSeed} = theme;
|
||||||
|
|
||||||
|
const mapToken = defaultAlgorithm(defaultSeed);
|
||||||
|
const v4Token = convertLegacyToken(mapToken);
|
||||||
|
|
||||||
|
// TODO, At the moment we are using less in the Dashboard. Once we remove this we can remove the less processor entirely.
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
// {
|
||||||
plugin: SentryWebpackPlugin,
|
// plugin: SentryWebpackPlugin,
|
||||||
options: {
|
// options: {
|
||||||
// sentry-cli configuration
|
// // sentry-cli configuration
|
||||||
authToken:
|
// authToken:
|
||||||
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
// "6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
||||||
org: "snapt-software",
|
// org: "snapt-software",
|
||||||
project: "rome-online",
|
// project: "imexonline",
|
||||||
release: process.env.REACT_APP_GIT_SHA,
|
// release: process.env.REACT_APP_GIT_SHA,
|
||||||
|
//
|
||||||
// webpack-specific configuration
|
// // webpack-specific configuration
|
||||||
include: ".",
|
// include: ".",
|
||||||
ignore: ["node_modules", "webpack.config.js"],
|
// ignore: ["node_modules", "webpack.config.js"],
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
plugin: CracoLessPlugin,
|
plugin: CracoLessPlugin,
|
||||||
options: {
|
options: {
|
||||||
lessLoaderOptions: {
|
lessLoaderOptions: {
|
||||||
lessOptions: {
|
lessOptions: {
|
||||||
modifyVars: {
|
modifyVars: {...v4Token},
|
||||||
...(process.env.NODE_ENV === "development"
|
javascriptEnabled: true,
|
||||||
? { "@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,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
},
|
webpack: {
|
||||||
],
|
configure: (webpackConfig) => {
|
||||||
webpack: {
|
return {
|
||||||
configure: (webpackConfig) => ({
|
...webpackConfig,
|
||||||
...webpackConfig,
|
// Required for Dev Server
|
||||||
optimization: {
|
devServer: {
|
||||||
...webpackConfig.optimization,
|
...webpackConfig.devServer,
|
||||||
// Workaround for CircleCI bug caused by the number of CPUs shown
|
allowedHosts: 'all',
|
||||||
// https://github.com/facebook/create-react-app/issues/8320
|
},
|
||||||
minimizer: webpackConfig.optimization.minimizer.map((item) => {
|
optimization: {
|
||||||
if (item instanceof TerserPlugin) {
|
...webpackConfig.optimization,
|
||||||
item.options.parallel = 2;
|
// 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;
|
return item;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}),
|
};
|
||||||
},
|
},
|
||||||
devtool: "source-map",
|
},
|
||||||
|
devtool: "source-map",
|
||||||
};
|
};
|
||||||
|
|||||||
17
client/cypress.config.js
Normal file
17
client/cypress.config.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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',
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"baseUrl": "http://localhost:3000",
|
|
||||||
"experimentalStudio": true,
|
|
||||||
"env": {
|
|
||||||
"FIREBASE_USERNAME": "cypress@imex.test",
|
|
||||||
"FIREBASE_PASSWORD": "cypress"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,81 +2,23 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/ro-favicon.png" />
|
<link rel="icon" href="/favicon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#002366" />
|
<meta name="theme-color" content="#002366" />
|
||||||
<meta name="description" content="Rome Online" />
|
<meta name="description" content="ImEX Online" />
|
||||||
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
||||||
|
<link rel="apple-touch-icon" href="public/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-->
|
<script type="text/javascript">
|
||||||
|
window.$crisp = [];
|
||||||
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001 party="LiveChat528346"></call-us-selector>
|
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
|
||||||
|
(function () {
|
||||||
<!--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-->
|
d = document;
|
||||||
|
s = d.createElement("script");
|
||||||
<!--<call-us
|
s.src = "https://client.crisp.chat/l.js";
|
||||||
|
s.async = 1;
|
||||||
phonesystem-url=https://rometech.east.3cx.us:5001
|
d.getElementsByTagName("head")[0].appendChild(s);
|
||||||
|
})();
|
||||||
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
|
</script>
|
||||||
|
|
||||||
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>
|
<script>
|
||||||
!(function () {
|
!(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
@@ -125,7 +67,7 @@ enable-ga="false"
|
|||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
-->
|
-->
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
@@ -135,10 +77,12 @@ enable-ga="false"
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
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`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>Rome Online</title>
|
<title>ImEX Online</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script type="module" src="src/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -8872,13 +8872,13 @@
|
|||||||
│ ├─ email: luis@luisrudge.net
|
│ ├─ email: luis@luisrudge.net
|
||||||
│ ├─ path: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-flexbugs-fixes
|
│ ├─ 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
|
│ └─ licenseFile: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-flexbugs-fixes/LICENSE
|
||||||
├─ postcss-focus-visible@4.0.0
|
├─ postcss-focus-open@4.0.0
|
||||||
│ ├─ licenses: CC0-1.0
|
│ ├─ licenses: CC0-1.0
|
||||||
│ ├─ repository: https://github.com/jonathantneal/postcss-focus-visible
|
│ ├─ repository: https://github.com/jonathantneal/postcss-focus-open
|
||||||
│ ├─ publisher: Jonathan Neal
|
│ ├─ publisher: Jonathan Neal
|
||||||
│ ├─ email: jonathantneal@hotmail.com
|
│ ├─ email: jonathantneal@hotmail.com
|
||||||
│ ├─ path: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-visible
|
│ ├─ path: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-open
|
||||||
│ └─ licenseFile: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-visible/LICENSE.md
|
│ └─ licenseFile: /Users/pfic/Documents/Development/bodyshop/client/node_modules/postcss-focus-open/LICENSE.md
|
||||||
├─ postcss-focus-within@3.0.0
|
├─ postcss-focus-within@3.0.0
|
||||||
│ ├─ licenses: CC0-1.0
|
│ ├─ licenses: CC0-1.0
|
||||||
│ ├─ repository: https://github.com/jonathantneal/postcss-focus-within
|
│ ├─ repository: https://github.com/jonathantneal/postcss-focus-within
|
||||||
|
|||||||
26120
client/package-lock.json
generated
26120
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,100 +1,112 @@
|
|||||||
{
|
{
|
||||||
"name": "bodyshop",
|
"name": "bodyshop",
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
|
"engines": {
|
||||||
|
"node": "18.18.2"
|
||||||
|
},
|
||||||
|
"type": "commonjs",
|
||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.7.9",
|
"@ant-design/compatible": "^5.1.2",
|
||||||
|
"@ant-design/pro-layout": "^7.17.16",
|
||||||
|
"@apollo/client": "^3.9.0",
|
||||||
"@asseinfo/react-kanban": "^2.2.0",
|
"@asseinfo/react-kanban": "^2.2.0",
|
||||||
"@craco/craco": "^7.0.0",
|
"@craco/craco": "^7.1.0",
|
||||||
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
"@fingerprintjs/fingerprintjs": "^4.2.2",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@sentry/react": "^7.40.0",
|
"@reduxjs/toolkit": "^2.1.0",
|
||||||
"@sentry/tracing": "^7.40.0",
|
"@sentry/cli": "^2.27.0",
|
||||||
"@splitsoftware/splitio-react": "^1.8.1",
|
"@sentry/react": "^7.99.0",
|
||||||
"@tanem/react-nprogress": "^5.0.8",
|
"@sentry/tracing": "^7.99.0",
|
||||||
"antd": "^4.24.8",
|
"@splitsoftware/splitio-react": "^1.11.0",
|
||||||
|
"@tanem/react-nprogress": "^5.0.51",
|
||||||
|
"@vitejs/plugin-legacy": "^5.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"@vitejs/plugin-react-refresh": "^1.3.6",
|
||||||
|
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||||
|
"antd": "^5.13.3",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"axios": "^1.3.4",
|
"apollo-link-sentry": "^3.3.0",
|
||||||
"craco-less": "^2.0.0",
|
"axios": "^1.6.7",
|
||||||
|
"consola": "^3.2.3",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"dayjs-business-days2": "^1.2.2",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.4.1",
|
||||||
"enquire-js": "^0.2.1",
|
"enquire-js": "^0.2.1",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
|
"esbuild": "^0.20.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"firebase": "^9.17.1",
|
"firebase": "^10.7.2",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"i18next": "^22.4.10",
|
"i18next": "^23.8.1",
|
||||||
"i18next-browser-languagedetector": "^7.0.1",
|
"i18next-browser-languagedetector": "^7.0.2",
|
||||||
"jsoneditor": "^9.9.0",
|
"jsoneditor": "^10.0.0",
|
||||||
"jsreport-browser-client-dist": "^1.3.0",
|
"jsreport-browser-client-dist": "^1.3.0",
|
||||||
"libphonenumber-js": "^1.10.21",
|
"libphonenumber-js": "^1.10.54",
|
||||||
"logrocket": "^3.0.1",
|
"logrocket": "^7.0.0",
|
||||||
"markerjs2": "^2.28.1",
|
"markerjs2": "^2.32.0",
|
||||||
"moment-business-days": "^1.2.0",
|
|
||||||
"moment-timezone": "^0.5.41",
|
|
||||||
"normalize-url": "^8.0.0",
|
"normalize-url": "^8.0.0",
|
||||||
"phone": "^3.1.35",
|
"phone": "^3.1.42",
|
||||||
"preval.macro": "^5.0.0",
|
"preval.macro": "^5.0.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^7.1.3",
|
"query-string": "^8.1.0",
|
||||||
"rc-queue-anim": "^2.0.0",
|
"rc-queue-anim": "^2.0.0",
|
||||||
"rc-scroll-anim": "^2.7.6",
|
"rc-scroll-anim": "^2.7.6",
|
||||||
"react": "^17.0.2",
|
"react": "^18.2.0",
|
||||||
"react-big-calendar": "^1.6.8",
|
"react-big-calendar": "^1.8.7",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^4.1.1",
|
"react-cookie": "^7.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.2.0",
|
||||||
"react-drag-listview": "^0.2.1",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.0",
|
"react-grid-gallery": "^1.0.0",
|
||||||
"react-grid-layout": "^1.3.4",
|
"react-grid-layout": "1.3.4",
|
||||||
"react-i18next": "^12.2.0",
|
"react-i18next": "^14.0.1",
|
||||||
"react-icons": "^4.7.1",
|
"react-icons": "^5.0.1",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-image-lightbox": "^5.1.4",
|
||||||
"react-intersection-observer": "^9.4.3",
|
"react-intersection-observer": "^9.5.3",
|
||||||
"react-number-format": "^5.1.3",
|
"react-markdown": "^9.0.1",
|
||||||
"react-redux": "^8.0.5",
|
"react-number-format": "^5.1.4",
|
||||||
"react-resizable": "^3.0.4",
|
"react-redux": "^9.1.0",
|
||||||
"react-router-dom": "^5.3.0",
|
"react-resizable": "^3.0.5",
|
||||||
"react-scripts": "^5.0.1",
|
"react-router-dom": "^6.21.2",
|
||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-sublime-video": "^0.2.5",
|
"react-sublime-video": "^0.2.5",
|
||||||
"react-virtualized": "^9.22.3",
|
"react-virtualized": "^9.22.5",
|
||||||
"recharts": "^2.4.3",
|
"recharts": "^2.11.0",
|
||||||
"redux": "^4.2.1",
|
"redux": "^5.0.1",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.2.2",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^4.1.7",
|
"reselect": "^5.1.0",
|
||||||
"sass": "^1.58.3",
|
"sass": "^1.70.0",
|
||||||
"socket.io-client": "^4.6.1",
|
"socket.io-client": "^4.7.4",
|
||||||
"styled-components": "^5.3.6",
|
"styled-components": "^6.1.8",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"web-vitals": "^2.1.4",
|
"terser-webpack-plugin": "^5.3.10",
|
||||||
"workbox-background-sync": "^6.5.3",
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
"workbox-broadcast-update": "^6.5.3",
|
"web-vitals": "^3.5.2",
|
||||||
"workbox-cacheable-response": "^6.5.3",
|
"workbox-core": "^7.0.0",
|
||||||
"workbox-core": "^6.5.3",
|
"workbox-expiration": "^7.0.0",
|
||||||
"workbox-expiration": "^6.5.3",
|
"workbox-navigation-preload": "^7.0.0",
|
||||||
"workbox-google-analytics": "^6.5.3",
|
"workbox-precaching": "^7.0.0",
|
||||||
"workbox-navigation-preload": "^6.5.3",
|
"workbox-routing": "^7.0.0",
|
||||||
"workbox-precaching": "^6.5.3",
|
"workbox-strategies": "^7.0.0",
|
||||||
"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"
|
"yauzl": "^2.10.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||||
"start": "craco start",
|
"start": "vite",
|
||||||
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
"build": "cross-env-shell VITE_APP_GIT_SHA=\\\"`git rev-parse --short HEAD`\\\" vite build && npm run sentry:sourcemaps",
|
||||||
"build:test": "env-cmd -f .env.test npm run build",
|
"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!'",
|
"build-deploy:test": "npm run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
|
||||||
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
"buildcra": "cross-env-shell VITE_APP_GIT_SHA=\\\"`git rev-parse --short HEAD`\\\" vite build",
|
||||||
"test": "cypress open",
|
"test": "cypress open",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular ."
|
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
|
||||||
|
"eulaize": "node src/utils/eulaize.js",
|
||||||
|
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -119,12 +131,29 @@
|
|||||||
"react-error-overlay": "6.0.9"
|
"react-error-overlay": "6.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sentry/webpack-plugin": "^1.20.0",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@testing-library/cypress": "^8.0.3",
|
"@babel/preset-react": "^7.23.3",
|
||||||
"cypress": "^10.3.1",
|
"@emotion/babel-plugin": "^11.11.0",
|
||||||
"eslint-plugin-cypress": "^2.12.1",
|
"@emotion/react": "^11.11.3",
|
||||||
|
"@sentry/webpack-plugin": "^2.10.3",
|
||||||
|
"@swc/core": "^1.3.107",
|
||||||
|
"@swc/plugin-styled-components": "^1.5.108",
|
||||||
|
"@testing-library/cypress": "^10.0.1",
|
||||||
|
"browserslist": "^4.22.3",
|
||||||
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
|
"craco-less": "^3.0.1",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"cypress": "^13.6.4",
|
||||||
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
|
"memfs": "^4.6.0",
|
||||||
|
"os-browserify": "^0.3.0",
|
||||||
"react-error-overlay": "6.0.11",
|
"react-error-overlay": "6.0.11",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.2"
|
"source-map-explorer": "^2.5.3",
|
||||||
|
"vite": "^5.0.11",
|
||||||
|
"vite-plugin-babel": "^1.2.0",
|
||||||
|
"vite-plugin-legacy": "^2.1.0",
|
||||||
|
"vite-plugin-node-polyfills": "^0.19.0",
|
||||||
|
"vite-plugin-style-import": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ This package contains the following license and notice below:
|
|||||||
# @firebase/logger
|
# @firebase/logger
|
||||||
|
|
||||||
This package serves as the base of all logging in the JS SDK. Any logging that
|
This package serves as the base of all logging in the JS SDK. Any logging that
|
||||||
is intended to be visible to Firebase end developers should go through this
|
is intended to be open to Firebase end developers should go through this
|
||||||
module.
|
module.
|
||||||
|
|
||||||
## Basic Usage
|
## 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.
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
to the extent that it includes a convenient and prominently visible
|
to the extent that it includes a convenient and prominently open
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
tells the user that there is no warranty for the work (except to the
|
tells the user that there is no warranty for the work (except to the
|
||||||
extent that warranties are provided), that licensees may convey 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-dir-pseudo-class@5.0.0
|
||||||
- postcss-double-position-gradients@1.0.0
|
- postcss-double-position-gradients@1.0.0
|
||||||
- postcss-env-function@2.0.2
|
- postcss-env-function@2.0.2
|
||||||
- postcss-focus-visible@4.0.0
|
- postcss-focus-open@4.0.0
|
||||||
- postcss-focus-within@3.0.0
|
- postcss-focus-within@3.0.0
|
||||||
- postcss-gap-properties@2.0.0
|
- postcss-gap-properties@2.0.0
|
||||||
- postcss-image-set-function@3.0.1
|
- postcss-image-set-function@3.0.1
|
||||||
@@ -1699,7 +1699,7 @@ This package contains the following license and notice below:
|
|||||||
# @firebase/logger
|
# @firebase/logger
|
||||||
|
|
||||||
This package serves as the base of all logging in the JS SDK. Any logging that
|
This package serves as the base of all logging in the JS SDK. Any logging that
|
||||||
is intended to be visible to Firebase end developers should go through this
|
is intended to be open to Firebase end developers should go through this
|
||||||
module.
|
module.
|
||||||
|
|
||||||
## Basic Usage
|
## 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.
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
An interactive user interface displays "Appropriate Legal Notices"
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
to the extent that it includes a convenient and prominently visible
|
to the extent that it includes a convenient and prominently open
|
||||||
feature that (1) displays an appropriate copyright notice, and (2)
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
tells the user that there is no warranty for the work (except to the
|
tells the user that there is no warranty for the work (except to the
|
||||||
extent that warranties are provided), that licensees may convey the
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
|||||||
@@ -28,17 +28,6 @@ switch (this.location.hostname) {
|
|||||||
// measurementId: "${config.measurementId}",
|
// measurementId: "${config.measurementId}",
|
||||||
};
|
};
|
||||||
break;
|
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":
|
case "imex.online":
|
||||||
default:
|
default:
|
||||||
firebaseConfig = {
|
firebaseConfig = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"short_name": "Rome Online",
|
"short_name": "ImEX Online",
|
||||||
"name": "Rome Online",
|
"name": "ImEX Online",
|
||||||
"description": "The ultimate bodyshop management system.",
|
"description": "The ultimate bodyshop management system.",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 376 B |
@@ -1,50 +1,53 @@
|
|||||||
import { ApolloProvider } from "@apollo/client";
|
import {ApolloProvider} from "@apollo/client";
|
||||||
import { SplitFactory, SplitSdk } from "@splitsoftware/splitio-react";
|
import {SplitFactoryProvider, SplitSdk,} from '@splitsoftware/splitio-react';
|
||||||
import { ConfigProvider } from "antd";
|
import {ConfigProvider} from "antd";
|
||||||
import enLocale from "antd/es/locale/en_US";
|
import enLocale from "antd/es/locale/en_US";
|
||||||
import moment from "moment";
|
import dayjs from "../utils/day";
|
||||||
|
import 'dayjs/locale/en';
|
||||||
import React from "react";
|
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 GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||||
import client from "../utils/GraphQLClient";
|
import client from "../utils/GraphQLClient";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
moment.locale("en-US");
|
import themeProvider from "./themeProvider";
|
||||||
|
|
||||||
export const factory = SplitSdk({
|
dayjs.locale("en");
|
||||||
core: {
|
|
||||||
authorizationKey: process.env.REACT_APP_SPLIT_API,
|
|
||||||
key: "anon",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function AppContainer() {
|
const config = {
|
||||||
const { t } = useTranslation();
|
core: {
|
||||||
|
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
||||||
|
key: "anon",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export const factory = SplitSdk(config);
|
||||||
|
|
||||||
return (
|
|
||||||
<ApolloProvider client={client}>
|
function AppContainer() {
|
||||||
<ConfigProvider
|
const {t} = useTranslation();
|
||||||
//componentSize="small"
|
|
||||||
input={{ autoComplete: "new-password" }}
|
return (
|
||||||
locale={enLocale}
|
<ApolloProvider client={client}>
|
||||||
theme={{
|
<ConfigProvider
|
||||||
token: {
|
//componentSize="small"
|
||||||
colorPrimary: "#326ade",
|
input={{autoComplete: "new-password"}}
|
||||||
colorInfo: "#326ade"
|
locale={enLocale}
|
||||||
},
|
theme={themeProvider}
|
||||||
}}
|
form={{
|
||||||
form={{
|
validateMessages: {
|
||||||
validateMessages: {
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
required: t("general.validation.required", {label: "${label}"}),
|
||||||
required: t("general.validation.required", { label: "${label}" }),
|
},
|
||||||
},
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<GlobalLoadingBar/>
|
||||||
<GlobalLoadingBar />
|
<SplitFactoryProvider factory={factory}>
|
||||||
<SplitFactory factory={factory}>
|
<App/>
|
||||||
<App />
|
</SplitFactoryProvider>
|
||||||
</SplitFactory>
|
</ConfigProvider>
|
||||||
</ConfigProvider>
|
</ApolloProvider>
|
||||||
</ApolloProvider>
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Sentry.withProfiler(AppContainer);
|
||||||
|
|||||||
@@ -1,161 +1,156 @@
|
|||||||
import { useClient } from "@splitsoftware/splitio-react";
|
import {useSplitClient} from "@splitsoftware/splitio-react";
|
||||||
import { Button, Result } from "antd";
|
import {Button, Result} from "antd";
|
||||||
import LogRocket from "logrocket";
|
import LogRocket from "logrocket";
|
||||||
import React, { lazy, Suspense, useEffect } from "react";
|
import React, {lazy, Suspense, useEffect, useState} from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import { Route, Switch } from "react-router-dom";
|
import {Route, Routes} from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||||
|
|
||||||
//Component Imports
|
//Component Imports
|
||||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
||||||
import LandingPage from "../pages/landing/landing.page";
|
import LandingPage from "../pages/landing/landing.page";
|
||||||
import TechPageContainer from "../pages/tech/tech.page.container";
|
import TechPageContainer from "../pages/tech/tech.page.container";
|
||||||
import { setOnline } from "../redux/application/application.actions";
|
import {setOnline} from "../redux/application/application.actions";
|
||||||
import { selectOnline } from "../redux/application/application.selectors";
|
import {selectOnline} from "../redux/application/application.selectors";
|
||||||
import { checkUserSession } from "../redux/user/user.actions";
|
import {checkUserSession} from "../redux/user/user.actions";
|
||||||
import {
|
import {selectBodyshop, selectCurrentEula, selectCurrentUser,} from "../redux/user/user.selectors";
|
||||||
selectBodyshop,
|
import PrivateRoute from "../components/PrivateRoute";
|
||||||
selectCurrentUser,
|
|
||||||
} from "../redux/user/user.selectors";
|
|
||||||
import PrivateRoute from "../utils/private-route";
|
|
||||||
import "./App.styles.scss";
|
import "./App.styles.scss";
|
||||||
|
import handleBeta from "../utils/betaHandler";
|
||||||
|
import Eula from "../components/eula/eula.component";
|
||||||
|
|
||||||
const ResetPassword = lazy(() =>
|
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 ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||||
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
||||||
|
|
||||||
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
||||||
const MobilePaymentContainer = lazy(() =>
|
const MobilePaymentContainer = lazy(() =>
|
||||||
import("../pages/mobile-payment/mobile-payment.container")
|
import("../pages/mobile-payment/mobile-payment.container")
|
||||||
);
|
);
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
online: selectOnline,
|
online: selectOnline,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
|
currentEula: selectCurrentEula
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
checkUserSession: () => dispatch(checkUserSession()),
|
checkUserSession: () => dispatch(checkUserSession()),
|
||||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function App({
|
export function App({bodyshop, checkUserSession, currentUser, online, setOnline, currentEula}) {
|
||||||
bodyshop,
|
const client = useSplitClient().client;
|
||||||
checkUserSession,
|
const [listenersAdded, setListenersAdded] = useState(false)
|
||||||
currentUser,
|
const {t} = useTranslation();
|
||||||
online,
|
|
||||||
setOnline,
|
|
||||||
}) {
|
|
||||||
const client = useClient();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!navigator.onLine) {
|
|
||||||
setOnline(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkUserSession();
|
useEffect(() => {
|
||||||
}, [checkUserSession, setOnline]);
|
if (!navigator.onLine) {
|
||||||
|
setOnline(false);
|
||||||
//const b = Grid.useBreakpoint();
|
|
||||||
// console.log("Breakpoints:", b);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
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]);
|
|
||||||
|
|
||||||
if (currentUser.authorized === null) {
|
|
||||||
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
}
|
}
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
checkUserSession();
|
||||||
<Switch>
|
}, [checkUserSession, setOnline]);
|
||||||
<Suspense fallback={<LoadingSpinner />}>
|
|
||||||
<ErrorBoundary>
|
//const b = Grid.useBreakpoint();
|
||||||
<Route exact path="/" component={LandingPage} />
|
// console.log("Breakpoints:", b);
|
||||||
</ErrorBoundary>
|
|
||||||
<ErrorBoundary>
|
// Associate event listeners, memoize to prevent multiple listeners being added
|
||||||
<Route exact path="/signin" component={SignInPage} />
|
useEffect(() => {
|
||||||
</ErrorBoundary>
|
const offlineListener = (e) => {
|
||||||
<ErrorBoundary>
|
setOnline(false);
|
||||||
<Route exact path="/resetpassword" component={ResetPassword} />
|
}
|
||||||
</ErrorBoundary>
|
|
||||||
<ErrorBoundary>
|
const onlineListener = (e) => {
|
||||||
<Route exact path="/csi/:surveyId" component={CsiPage} />
|
setOnline(true);
|
||||||
</ErrorBoundary>
|
}
|
||||||
<ErrorBoundary>
|
|
||||||
<Route exact path="/disclaimer" component={DisclaimerPage} />
|
if (!listenersAdded) {
|
||||||
</ErrorBoundary>
|
console.log('Added events for offline and online');
|
||||||
<ErrorBoundary>
|
window.addEventListener("offline", offlineListener);
|
||||||
<Route
|
window.addEventListener("online", onlineListener);
|
||||||
exact
|
setListenersAdded(true);
|
||||||
path="/mp/:paymentIs"
|
}
|
||||||
component={MobilePaymentContainer}
|
|
||||||
/>
|
return () => {
|
||||||
</ErrorBoundary>
|
window.removeEventListener("offline", offlineListener);
|
||||||
<ErrorBoundary>
|
window.removeEventListener("online", onlineListener);
|
||||||
<PrivateRoute
|
}
|
||||||
isAuthorized={currentUser.authorized}
|
}, [setOnline, listenersAdded]);
|
||||||
path="/manage"
|
|
||||||
component={ManagePage}
|
useEffect(() => {
|
||||||
/>
|
if (currentUser.authorized && bodyshop) {
|
||||||
</ErrorBoundary>
|
client.setAttribute("imexshopid", bodyshop.imexshopid);
|
||||||
<ErrorBoundary>
|
|
||||||
<PrivateRoute
|
if (
|
||||||
isAuthorized={currentUser.authorized}
|
client.getTreatment("LogRocket_Tracking") === "on" ||
|
||||||
path="/tech"
|
window.location.hostname === 'beta.imex.online'
|
||||||
component={TechPageContainer}
|
) {
|
||||||
/>
|
console.log("LR Start");
|
||||||
</ErrorBoundary>
|
LogRocket.init("gvfvfw/bodyshopapp");
|
||||||
<ErrorBoundary>
|
}
|
||||||
<PrivateRoute
|
}
|
||||||
isAuthorized={currentUser.authorized}
|
}, [bodyshop, client, currentUser.authorized]);
|
||||||
path="/edit"
|
|
||||||
component={DocumentEditorContainer}
|
if (currentUser.authorized === null) {
|
||||||
/>
|
return <LoadingSpinner message={t("general.labels.loggingin")}/>;
|
||||||
</ErrorBoundary>
|
}
|
||||||
</Suspense>
|
|
||||||
</Switch>
|
handleBeta();
|
||||||
);
|
|
||||||
|
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>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (currentEula && !currentUser.eulaIsAccepted) {
|
||||||
|
return <Eula/>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any route that is not assigned and matched will default to the Landing Page component
|
||||||
|
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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
//Global Styles.
|
//Global Styles.
|
||||||
@import "react-big-calendar/lib/sass/styles";
|
@import "react-big-calendar/lib/sass/styles";
|
||||||
|
|
||||||
|
.ant-menu-item-divider {
|
||||||
|
border-bottom: 1px solid #74695c !important;
|
||||||
|
}
|
||||||
|
|
||||||
.imex-table-header {
|
.imex-table-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -143,23 +147,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Update row highlighting on production board.
|
|
||||||
.ant-table-tbody > tr.ant-table-row:hover > td {
|
|
||||||
background: #e7f3ff !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-tbody > tr.ant-table-row-selected > td {
|
|
||||||
background: #e6f7ff !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.job-line-manual {
|
.job-line-manual {
|
||||||
color: tomato;
|
color: tomato;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
td.ant-table-column-sort {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
||||||
background-color: #f4f4f4;
|
background-color: #f4f4f4;
|
||||||
|
|||||||
60
client/src/App/themeProvider.js
Normal file
60
client/src/App/themeProvider.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import {defaultsDeep} from "lodash";
|
||||||
|
import {theme} from "antd";
|
||||||
|
|
||||||
|
const {defaultAlgorithm, darkAlgorithm} = theme;
|
||||||
|
|
||||||
|
let isDarkMode = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default theme
|
||||||
|
* @type {{components: {Menu: {itemDividerBorderColor: string}}}}
|
||||||
|
*/
|
||||||
|
const defaultTheme = {
|
||||||
|
components: {
|
||||||
|
Table: {
|
||||||
|
rowHoverBg: '#e7f3ff',
|
||||||
|
rowSelectedBg: '#e6f7ff',
|
||||||
|
headerSortHoverBg: 'transparent',
|
||||||
|
},
|
||||||
|
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 currentTheme = import.meta.env.DEV ? devTheme
|
||||||
|
: prodTheme;
|
||||||
|
|
||||||
|
const finaltheme = {
|
||||||
|
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||||
|
...defaultsDeep(currentTheme, defaultTheme)
|
||||||
|
}
|
||||||
|
export default finaltheme;
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB |
17
client/src/components/PrivateRoute.js
Normal file
17
client/src/components/PrivateRoute.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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";
|
import React from "react";
|
||||||
|
|
||||||
export default function AlertComponent(props) {
|
export default function AlertComponent(props) {
|
||||||
return <Alert {...props} />;
|
return <Alert {...props} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function AllocationsAssignmentComponent({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover content={popContent} visible={visibility}>
|
<Popover content={popContent} open={visibility}>
|
||||||
<Button onClick={() => setVisibility(true)}>
|
<Button onClick={() => setVisibility(true)}>
|
||||||
{t("allocations.actions.assign")}
|
{t("allocations.actions.assign")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default connect(
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover content={popContent} visible={visibility}>
|
<Popover content={popContent} open={visibility}>
|
||||||
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||||
{t("allocations.actions.assign")}
|
{t("allocations.actions.assign")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQuery } from "@apollo/client";
|
import {useMutation, useQuery} from "@apollo/client";
|
||||||
import { Button, Form, PageHeader, Popconfirm, Space } from "antd";
|
import {Button, Form, Popconfirm, Space} from "antd";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -20,148 +20,148 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import BillFormContainer from "../bill-form/bill-form.container";
|
import BillFormContainer from "../bill-form/bill-form.container";
|
||||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||||
|
import BillPrintButton from "../bill-print-button/bill-print-button.component";
|
||||||
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
|
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
|
||||||
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
||||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import BillDetailEditReturn from "./bill-detail-edit-return.component";
|
import BillDetailEditReturn from "./bill-detail-edit-return.component";
|
||||||
|
import {PageHeader} from "@ant-design/pro-layout";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPartsOrderContext: (context) =>
|
setPartsOrderContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
dispatch(setModalContext({context: context, modal: "partsOrder"})),
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
insertAuditTrail: ({jobid, operation}) =>
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
dispatch(insertAuditTrail({jobid, operation})),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(BillDetailEditcontainer);
|
)(BillDetailEditcontainer);
|
||||||
|
|
||||||
export function BillDetailEditcontainer({
|
export function BillDetailEditcontainer({setPartsOrderContext, insertAuditTrail, bodyshop,}) {
|
||||||
setPartsOrderContext,
|
const search = queryString.parse(useLocation().search);
|
||||||
insertAuditTrail,
|
|
||||||
bodyshop,
|
|
||||||
}) {
|
|
||||||
const search = queryString.parse(useLocation().search);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [visible, setVisible] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [updateLoading, setUpdateLoading] = useState(false);
|
const [updateLoading, setUpdateLoading] = useState(false);
|
||||||
const [update_bill] = useMutation(UPDATE_BILL);
|
const [update_bill] = useMutation(UPDATE_BILL);
|
||||||
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
|
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
|
||||||
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
|
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
|
||||||
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
|
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
|
||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
|
const {loading, error, data, refetch} = useQuery(QUERY_BILL_BY_PK, {
|
||||||
variables: { billid: search.billid },
|
variables: {billid: search.billid},
|
||||||
skip: !!!search.billid,
|
skip: !!!search.billid,
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//Find bill lines that were deleted.
|
// ... rest of the code remains the same
|
||||||
const deletedJobLines = [];
|
|
||||||
|
|
||||||
data.bills_by_pk.billlines.forEach((a) => {
|
const handleSave = () => {
|
||||||
const matchingRecord = billlines.find((b) => b.id === a.id);
|
//It's got a previously deducted bill line!
|
||||||
if (!matchingRecord) {
|
if (
|
||||||
deletedJobLines.push(a);
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
deletedJobLines.forEach((d) => {
|
const handleFinish = async (values) => {
|
||||||
updates.push(deleteBillLine({ variables: { id: d.id } }));
|
setUpdateLoading(true);
|
||||||
});
|
//let adjustmentsToInsert = {};
|
||||||
|
|
||||||
billlines.forEach((billline) => {
|
const {billlines, upload, ...bill} = values;
|
||||||
const { deductedfromlbr, inventories, jobline, ...il } = billline;
|
const updates = [];
|
||||||
delete il.__typename;
|
|
||||||
|
|
||||||
if (il.id) {
|
|
||||||
updates.push(
|
updates.push(
|
||||||
updateBillLine({
|
update_bill({
|
||||||
variables: {
|
variables: {billId: search.billid, bill: bill},
|
||||||
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);
|
billlines.forEach((l) => {
|
||||||
|
delete l.selected;
|
||||||
|
});
|
||||||
|
|
||||||
insertAuditTrail({
|
//Find bill lines that were deleted.
|
||||||
jobid: bill.jobid,
|
const deletedJobLines = [];
|
||||||
billid: search.billid,
|
|
||||||
operation: AuditTrailMapping.billupdated(bill.invoice_number),
|
|
||||||
});
|
|
||||||
|
|
||||||
await refetch();
|
data.bills_by_pk.billlines.forEach((a) => {
|
||||||
form.setFieldsValue(transformData(data));
|
const matchingRecord = billlines.find((b) => b.id === a.id);
|
||||||
form.resetFields();
|
if (!matchingRecord) {
|
||||||
setVisible(false);
|
deletedJobLines.push(a);
|
||||||
setUpdateLoading(false);
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
deletedJobLines.forEach((d) => {
|
||||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
updates.push(deleteBillLine({variables: {id: d.id}}));
|
||||||
|
});
|
||||||
|
|
||||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
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;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -176,11 +176,11 @@ export function BillDetailEditcontainer({
|
|||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<BillDetailEditReturn data={data} />
|
<BillDetailEditReturn data={data} />
|
||||||
|
<BillPrintButton billid={search.billid} />
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
visible={visible}
|
open={open}
|
||||||
onConfirm={() => form.submit()}
|
onConfirm={() => form.submit()}
|
||||||
onCancel={() => setVisible(false)}
|
onCancel={() => setOpen(false)}
|
||||||
okButtonProps={{ loading: updateLoading }}
|
okButtonProps={{ loading: updateLoading }}
|
||||||
title={t("bills.labels.editadjwarning")}
|
title={t("bills.labels.editadjwarning")}
|
||||||
>
|
>
|
||||||
@@ -207,45 +207,45 @@ export function BillDetailEditcontainer({
|
|||||||
>
|
>
|
||||||
<BillFormContainer form={form} billEdit disabled={exported} />
|
<BillFormContainer form={form} billEdit disabled={exported} />
|
||||||
|
|
||||||
{bodyshop.uselocalmediaserver ? (
|
{bodyshop.uselocalmediaserver ? (
|
||||||
<JobsDocumentsLocalGallery
|
<JobsDocumentsLocalGallery
|
||||||
job={{ id: data ? data.bills_by_pk.jobid : null }}
|
job={{id: data ? data.bills_by_pk.jobid : null}}
|
||||||
invoice_number={data ? data.bills_by_pk.invoice_number : null}
|
invoice_number={data ? data.bills_by_pk.invoice_number : null}
|
||||||
vendorid={data ? data.bills_by_pk.vendorid : null}
|
vendorid={data ? data.bills_by_pk.vendorid : null}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<JobDocumentsGallery
|
<JobDocumentsGallery
|
||||||
jobId={data ? data.bills_by_pk.jobid : null}
|
jobId={data ? data.bills_by_pk.jobid : null}
|
||||||
billId={search.billid}
|
billId={search.billid}
|
||||||
documentsList={data ? data.bills_by_pk.documents : []}
|
documentsList={data ? data.bills_by_pk.documents : []}
|
||||||
billsCallback={refetch}
|
billsCallback={refetch}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Form>
|
|
||||||
</>
|
</>
|
||||||
)}
|
);
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const transformData = (data) => {
|
const transformData = (data) => {
|
||||||
return data
|
return data
|
||||||
? {
|
? {
|
||||||
...data.bills_by_pk,
|
...data.bills_by_pk,
|
||||||
|
|
||||||
billlines: data.bills_by_pk.billlines.map((i) => {
|
billlines: data.bills_by_pk.billlines.map((i) => {
|
||||||
return {
|
return {
|
||||||
...i,
|
...i,
|
||||||
joblineid: !!i.joblineid ? i.joblineid : "noline",
|
joblineid: !!i.joblineid ? i.joblineid : "noline",
|
||||||
applicable_taxes: {
|
applicable_taxes: {
|
||||||
federal:
|
federal:
|
||||||
(i.applicable_taxes && i.applicable_taxes.federal) || false,
|
(i.applicable_taxes && i.applicable_taxes.federal) || false,
|
||||||
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
|
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
|
||||||
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
|
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
|
date: data.bills_by_pk ? dayjs(data.bills_by_pk.date) : null,
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import queryString from "query-string";
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
@@ -33,10 +33,10 @@ export function BillDetailEditReturn({
|
|||||||
disabled,
|
disabled,
|
||||||
}) {
|
}) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const history = useHistory();
|
const history = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [visible, setVisible] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const handleFinish = ({ billlines }) => {
|
const handleFinish = ({ billlines }) => {
|
||||||
const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id);
|
const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id);
|
||||||
@@ -67,18 +67,18 @@ export function BillDetailEditReturn({
|
|||||||
});
|
});
|
||||||
delete search.billid;
|
delete search.billid;
|
||||||
|
|
||||||
history.push({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
setVisible(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible === false) form.resetFields();
|
if (open === false) form.resetFields();
|
||||||
}, [visible, form]);
|
}, [open, form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
open={open}
|
||||||
onCancel={() => setVisible(false)}
|
onCancel={() => setOpen(false)}
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
title={t("bills.actions.return")}
|
title={t("bills.actions.return")}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
@@ -175,7 +175,7 @@ export function BillDetailEditReturn({
|
|||||||
<Button
|
<Button
|
||||||
disabled={data.bills_by_pk.is_credit_memo || disabled}
|
disabled={data.bills_by_pk.is_credit_memo || disabled}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setVisible(true);
|
setOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("bills.actions.return")}
|
{t("bills.actions.return")}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Drawer, Grid } from "antd";
|
import { Drawer, Grid } from "antd";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import BillDetailEditComponent from "./bill-detail-edit-component";
|
import BillDetailEditComponent from "./bill-detail-edit-component";
|
||||||
|
|
||||||
export default function BillDetailEditcontainer() {
|
export default function BillDetailEditcontainer() {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const history = useHistory();
|
const history = useNavigate();
|
||||||
|
|
||||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||||
.filter((screen) => !!screen[1])
|
.filter((screen) => !!screen[1])
|
||||||
@@ -29,10 +29,10 @@ export default function BillDetailEditcontainer() {
|
|||||||
width={drawerPercentage}
|
width={drawerPercentage}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
delete search.billid;
|
delete search.billid;
|
||||||
history.push({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
}}
|
}}
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
visible={search.billid}
|
open={search.billid}
|
||||||
>
|
>
|
||||||
<BillDetailEditComponent />
|
<BillDetailEditComponent />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useApolloClient, useMutation } from "@apollo/client";
|
import { useApolloClient, useMutation } from "@apollo/client";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
|
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
@@ -64,19 +63,9 @@ function BillEnterModalContainer({
|
|||||||
"enter_bill_generate_label",
|
"enter_bill_generate_label",
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
const { Enhanced_Payroll } = useTreatments(
|
|
||||||
["Enhanced_Payroll"],
|
|
||||||
{},
|
|
||||||
bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
const formValues = useMemo(() => {
|
const formValues = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
...billEnterModal.context.bill,
|
...billEnterModal.context.bill,
|
||||||
//Added as a part of IO-2436 for capturing parts price changes.
|
|
||||||
billlines: billEnterModal.context?.bill?.billlines?.map((line) => ({
|
|
||||||
...line,
|
|
||||||
original_actual_price: line.actual_price,
|
|
||||||
})),
|
|
||||||
jobid:
|
jobid:
|
||||||
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
|
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
|
||||||
federal_tax_rate:
|
federal_tax_rate:
|
||||||
@@ -109,7 +98,6 @@ function BillEnterModalContainer({
|
|||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
let adjustmentsToInsert = {};
|
let adjustmentsToInsert = {};
|
||||||
let payrollAdjustmentsToInsert = [];
|
|
||||||
|
|
||||||
const r1 = await insertBill({
|
const r1 = await insertBill({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -125,33 +113,14 @@ function BillEnterModalContainer({
|
|||||||
lbr_adjustment,
|
lbr_adjustment,
|
||||||
location: lineLocation,
|
location: lineLocation,
|
||||||
part_type,
|
part_type,
|
||||||
create_ppc,
|
|
||||||
original_actual_price,
|
|
||||||
...restI
|
...restI
|
||||||
} = i;
|
} = i;
|
||||||
|
|
||||||
if (Enhanced_Payroll.treatment === "on") {
|
if (deductedfromlbr) {
|
||||||
if (
|
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
|
||||||
deductedfromlbr &&
|
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
|
||||||
true //payroll is on
|
restI.actual_price / lbr_adjustment.rate;
|
||||||
) {
|
|
||||||
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 {
|
return {
|
||||||
...restI,
|
...restI,
|
||||||
deductedfromlbr: deductedfromlbr,
|
deductedfromlbr: deductedfromlbr,
|
||||||
@@ -177,20 +146,6 @@ function BillEnterModalContainer({
|
|||||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
|
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);
|
const adjKeys = Object.keys(adjustmentsToInsert);
|
||||||
if (adjKeys.length > 0) {
|
if (adjKeys.length > 0) {
|
||||||
//Query the adjustments, merge, and update them.
|
//Query the adjustments, merge, and update them.
|
||||||
@@ -299,14 +254,6 @@ function BillEnterModalContainer({
|
|||||||
location: li.location || location,
|
location: li.location || location,
|
||||||
status:
|
status:
|
||||||
bodyshop.md_order_statuses.default_received || "Received*",
|
bodyshop.md_order_statuses.default_received || "Received*",
|
||||||
//Added parts price changes.
|
|
||||||
...(li.create_ppc &&
|
|
||||||
li.original_actual_price !== li.actual_price
|
|
||||||
? {
|
|
||||||
act_price_before_ppc: li.original_actual_price,
|
|
||||||
act_price: li.actual_price,
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -375,12 +322,12 @@ function BillEnterModalContainer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (enterAgain) {
|
if (enterAgain) {
|
||||||
// form.resetFields();
|
form.resetFields();
|
||||||
|
form.resetFields();
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...formValues,
|
...formValues,
|
||||||
billlines: [],
|
billlines: [],
|
||||||
});
|
});
|
||||||
form.resetFields();
|
|
||||||
} else {
|
} else {
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
}
|
}
|
||||||
@@ -399,18 +346,18 @@ function BillEnterModalContainer({
|
|||||||
}, [enterAgain, form]);
|
}, [enterAgain, form]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (billEnterModal.visible) {
|
if (billEnterModal.open) {
|
||||||
form.setFieldsValue(formValues);
|
form.setFieldsValue(formValues);
|
||||||
} else {
|
} else {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
}
|
}
|
||||||
}, [billEnterModal.visible, form, formValues]);
|
}, [billEnterModal.open, form, formValues]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t("bills.labels.new")}
|
title={t("bills.labels.new")}
|
||||||
width={"98%"}
|
width={"98%"}
|
||||||
visible={billEnterModal.visible}
|
open={billEnterModal.open}
|
||||||
okText={t("general.actions.save")}
|
okText={t("general.actions.save")}
|
||||||
keyboard="false"
|
keyboard="false"
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
@@ -455,9 +402,6 @@ function BillEnterModalContainer({
|
|||||||
setEnterAgain(false);
|
setEnterAgain(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button onClick={() => console.log(form.getFieldsValue("billlines"))}>
|
|
||||||
get billlines
|
|
||||||
</button>
|
|
||||||
<BillFormContainer
|
<BillFormContainer
|
||||||
form={form}
|
form={form}
|
||||||
disableInvNumber={billEnterModal.context.disableInvNumber}
|
disableInvNumber={billEnterModal.context.disableInvNumber}
|
||||||
|
|||||||
@@ -1,26 +1,16 @@
|
|||||||
import Icon, { UploadOutlined } from "@ant-design/icons";
|
import Icon, {UploadOutlined} from "@ant-design/icons";
|
||||||
import { useApolloClient } from "@apollo/client";
|
import {useApolloClient} from "@apollo/client";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||||
import {
|
import {Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload,} from "antd";
|
||||||
Alert,
|
import dayjs from "../../utils/day";
|
||||||
Divider,
|
import React, {useEffect, useState} from "react";
|
||||||
Form,
|
import {useTranslation} from "react-i18next";
|
||||||
Input,
|
import {MdOpenInNew} from "react-icons/md";
|
||||||
Select,
|
import {connect} from "react-redux";
|
||||||
Space,
|
import {Link} from "react-router-dom";
|
||||||
Statistic,
|
import {createStructuredSelector} from "reselect";
|
||||||
Switch,
|
import {CHECK_BILL_INVOICE_NUMBER} from "../../graphql/bills.queries";
|
||||||
Upload,
|
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||||
} 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 AlertComponent from "../alert/alert.component";
|
||||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||||
@@ -30,309 +20,307 @@ import JobSearchSelect from "../job-search-select/job-search-select.component";
|
|||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import BillFormLines from "./bill-form.lines.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({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({});
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
|
|
||||||
export function BillFormComponent({
|
export function BillFormComponent({bodyshop, disabled, form, vendorAutoCompleteOptions, lineData, responsibilityCenters, loadLines, billEdit, disableInvNumber, job, loadOutstandingReturns, loadInventory, preferredMake}) {
|
||||||
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 handleVendorSelect = (props, opt) => {
|
const {t} = useTranslation();
|
||||||
setDiscount(opt.discount);
|
const client = useApolloClient();
|
||||||
|
const [discount, setDiscount] = useState(0);
|
||||||
|
|
||||||
opt &&
|
const { treatments: {Extended_Bill_Posting, ClosingPeriod} } = useSplitTreatments({
|
||||||
!billEdit &&
|
attributes: {},
|
||||||
loadOutstandingReturns({
|
names: ["Extended_Bill_Posting", "ClosingPeriod"],
|
||||||
variables: {
|
splitKey: bodyshop.imexshopid,
|
||||||
jobId: form.getFieldValue("jobid"),
|
});
|
||||||
vendorId: opt.value,
|
|
||||||
},
|
|
||||||
});
|
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 });
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (job) form.validateFields(["is_credit_memo"]);
|
if (job) form.validateFields(["is_credit_memo"]);
|
||||||
}, [job, form]);
|
}, [job, form]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const vendorId = form.getFieldValue("vendorid");
|
const vendorId = form.getFieldValue("vendorid");
|
||||||
if (vendorId && vendorAutoCompleteOptions) {
|
if (vendorId && vendorAutoCompleteOptions) {
|
||||||
const matchingVendors = vendorAutoCompleteOptions.filter(
|
const matchingVendors = vendorAutoCompleteOptions.filter(
|
||||||
(v) => v.id === vendorId
|
(v) => v.id === vendorId
|
||||||
);
|
);
|
||||||
if (matchingVendors.length === 1) {
|
if (matchingVendors.length === 1) {
|
||||||
setDiscount(matchingVendors[0].discount);
|
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: 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>
|
|
||||||
}
|
}
|
||||||
/>
|
}
|
||||||
))}
|
const jobId = form.getFieldValue("jobid");
|
||||||
<LayoutFormRow>
|
if (jobId) {
|
||||||
<Form.Item
|
loadLines({variables: {id: jobId}});
|
||||||
label={t("bills.fields.invoice_number")}
|
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
|
||||||
name="invoice_number"
|
loadOutstandingReturns({
|
||||||
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: {
|
variables: {
|
||||||
invoice_number: value,
|
jobId: jobId,
|
||||||
vendorid: vendorid,
|
vendorId: vendorId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (response.data.bills_aggregate.aggregate.count === 0) {
|
if (vendorId === bodyshop.inhousevendorid && !billEdit) {
|
||||||
return Promise.resolve();
|
loadInventory();
|
||||||
} else if (
|
}
|
||||||
response.data.bills_aggregate.nodes.length === 1 &&
|
}, [
|
||||||
response.data.bills_aggregate.nodes[0].id ===
|
form,
|
||||||
form.getFieldValue("id")
|
billEdit,
|
||||||
) {
|
loadOutstandingReturns,
|
||||||
return Promise.resolve();
|
loadInventory,
|
||||||
}
|
setDiscount,
|
||||||
return Promise.reject(
|
vendorAutoCompleteOptions,
|
||||||
t("bills.validation.unique_invoice_number")
|
loadLines,
|
||||||
);
|
bodyshop.inhousevendorid,
|
||||||
} 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"),
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
return (
|
||||||
!bodyshop.bill_allow_post_to_closed &&
|
<div>
|
||||||
job &&
|
<FormFieldsChanged form={form}/>
|
||||||
(job.status === bodyshop.md_ro_statuses.default_invoiced ||
|
<Form.Item
|
||||||
job.status === bodyshop.md_ro_statuses.default_exported ||
|
style={{display: "none"}}
|
||||||
job.status === bodyshop.md_ro_statuses.default_void) &&
|
name="isinhouse"
|
||||||
(value === false || !value)
|
valuePropName="checked"
|
||||||
) {
|
>
|
||||||
return Promise.reject(t("bills.labels.onlycmforinvoiced"));
|
<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"));
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
@@ -366,15 +354,13 @@ export function BillFormComponent({
|
|||||||
)}
|
)}
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow>
|
<LayoutFormRow>
|
||||||
{
|
<Form.Item
|
||||||
// <Form.Item
|
span={3}
|
||||||
// span={3}
|
label={t("bills.fields.federal_tax_rate")}
|
||||||
// label={t("bills.fields.federal_tax_rate")}
|
name="federal_tax_rate"
|
||||||
// name="federal_tax_rate"
|
>
|
||||||
// >
|
<CurrencyInput min={0} disabled={disabled} />
|
||||||
// <CurrencyInput min={0} disabled={disabled} />
|
</Form.Item>
|
||||||
// </Form.Item>
|
|
||||||
}
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
span={3}
|
span={3}
|
||||||
label={t("bills.fields.state_tax_rate")}
|
label={t("bills.fields.state_tax_rate")}
|
||||||
@@ -382,16 +368,23 @@ export function BillFormComponent({
|
|||||||
>
|
>
|
||||||
<CurrencyInput min={0} disabled={disabled} />
|
<CurrencyInput min={0} disabled={disabled} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{
|
<Form.Item
|
||||||
// <Form.Item
|
span={3}
|
||||||
// span={3}
|
label={t("bills.fields.local_tax_rate")}
|
||||||
// label={t("bills.fields.local_tax_rate")}
|
name="local_tax_rate"
|
||||||
// name="local_tax_rate"
|
>
|
||||||
// >
|
<CurrencyInput min={0} />
|
||||||
// <CurrencyInput min={0} />
|
</Form.Item>
|
||||||
// </Form.Item>
|
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
|
||||||
}
|
<Form.Item
|
||||||
<Form.Item shouldUpdate span={15}>
|
span={2}
|
||||||
|
label={t("bills.labels.federal_tax_exempt")}
|
||||||
|
name="federal_tax_exempt"
|
||||||
|
>
|
||||||
|
<Switch onChange={handleFederalTaxExemptSwitchToggle} />
|
||||||
|
</Form.Item>
|
||||||
|
) : null}
|
||||||
|
<Form.Item shouldUpdate span={13}>
|
||||||
{() => {
|
{() => {
|
||||||
const values = form.getFieldsValue([
|
const values = form.getFieldsValue([
|
||||||
"billlines",
|
"billlines",
|
||||||
@@ -409,32 +402,28 @@ export function BillFormComponent({
|
|||||||
totals = CalculateBillTotal(values);
|
totals = CalculateBillTotal(values);
|
||||||
if (!!totals)
|
if (!!totals)
|
||||||
return (
|
return (
|
||||||
<div>
|
<div align="right">
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("bills.labels.subtotal")}
|
title={t("bills.labels.subtotal")}
|
||||||
value={totals.subtotal.toFormat()}
|
value={totals.subtotal.toFormat()}
|
||||||
precision={2}
|
precision={2}
|
||||||
/>
|
/>
|
||||||
{
|
<Statistic
|
||||||
// <Statistic
|
title={t("bills.labels.federal_tax")}
|
||||||
// title={t("bills.labels.federal_tax")}
|
value={totals.federalTax.toFormat()}
|
||||||
// value={totals.federalTax.toFormat()}
|
precision={2}
|
||||||
// precision={2}
|
/>
|
||||||
// />
|
|
||||||
}
|
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("bills.labels.state_tax")}
|
title={t("bills.labels.state_tax")}
|
||||||
value={totals.stateTax.toFormat()}
|
value={totals.stateTax.toFormat()}
|
||||||
precision={2}
|
precision={2}
|
||||||
/>
|
/>
|
||||||
{
|
<Statistic
|
||||||
// <Statistic
|
title={t("bills.labels.local_tax")}
|
||||||
// title={t("bills.labels.local_tax")}
|
value={totals.localTax.toFormat()}
|
||||||
// value={totals.localTax.toFormat()}
|
precision={2}
|
||||||
// precision={2}
|
/>
|
||||||
// />
|
|
||||||
}
|
|
||||||
<Statistic
|
<Statistic
|
||||||
title={t("bills.labels.entered_total")}
|
title={t("bills.labels.entered_total")}
|
||||||
value={totals.enteredTotal.toFormat()}
|
value={totals.enteredTotal.toFormat()}
|
||||||
@@ -471,55 +460,55 @@ export function BillFormComponent({
|
|||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Divider orientation="left">{t("bills.labels.bill_lines")}</Divider>
|
<Divider orientation="left">{t("bills.labels.bill_lines")}</Divider>
|
||||||
|
|
||||||
{Extended_Bill_Posting.treatment === "on" ? (
|
{Extended_Bill_Posting.treatment === "on" ? (
|
||||||
<BillFormLinesExtended
|
<BillFormLinesExtended
|
||||||
lineData={lineData}
|
lineData={lineData}
|
||||||
discount={discount}
|
discount={discount}
|
||||||
form={form}
|
form={form}
|
||||||
responsibilityCenters={responsibilityCenters}
|
responsibilityCenters={responsibilityCenters}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<BillFormLines
|
<BillFormLines
|
||||||
lineData={lineData}
|
lineData={lineData}
|
||||||
discount={discount}
|
discount={discount}
|
||||||
form={form}
|
form={form}
|
||||||
responsibilityCenters={responsibilityCenters}
|
responsibilityCenters={responsibilityCenters}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
billEdit={billEdit}
|
billEdit={billEdit}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="upload"
|
name="upload"
|
||||||
label="Upload"
|
label="Upload"
|
||||||
style={{ display: billEdit ? "none" : null }}
|
style={{display: billEdit ? "none" : null}}
|
||||||
valuePropName="fileList"
|
valuePropName="fileList"
|
||||||
getValueFromEvent={(e) => {
|
getValueFromEvent={(e) => {
|
||||||
if (Array.isArray(e)) {
|
if (Array.isArray(e)) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
return e && e.fileList;
|
return e && e.fileList;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Upload.Dragger
|
<Upload.Dragger
|
||||||
multiple={true}
|
multiple={true}
|
||||||
name="logo"
|
name="logo"
|
||||||
beforeUpload={() => false}
|
beforeUpload={() => false}
|
||||||
listType="picture"
|
listType="picture"
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<p className="ant-upload-drag-icon">
|
<p className="ant-upload-drag-icon">
|
||||||
<UploadOutlined />
|
<UploadOutlined/>
|
||||||
</p>
|
</p>
|
||||||
<p className="ant-upload-text">
|
<p className="ant-upload-text">
|
||||||
Click or drag files to this area to upload.
|
Click or drag files to this area to upload.
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
</Upload.Dragger>
|
</Upload.Dragger>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillFormComponent);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillFormComponent);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -23,11 +23,11 @@ export function BillFormContainer({
|
|||||||
disabled,
|
disabled,
|
||||||
disableInvNumber,
|
disableInvNumber,
|
||||||
}) {
|
}) {
|
||||||
const { Simple_Inventory } = useTreatments(
|
const { treatments: {Simple_Inventory} } = useSplitTreatments({
|
||||||
["Simple_Inventory"],
|
attributes: {},
|
||||||
{},
|
names: ["Simple_Inventory"],
|
||||||
bodyshop && bodyshop.imexshopid
|
splitKey: bodyshop && bodyshop.imexshopid,
|
||||||
);
|
});
|
||||||
|
|
||||||
const { data: VendorAutoCompleteData } = useQuery(
|
const { data: VendorAutoCompleteData } = useQuery(
|
||||||
SEARCH_VENDOR_AUTOCOMPLETE,
|
SEARCH_VENDOR_AUTOCOMPLETE,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Checkbox,
|
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
@@ -42,17 +41,13 @@ export function BillEnterModalLinesComponent({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
||||||
const { Simple_Inventory } = useTreatments(
|
|
||||||
["Simple_Inventory"],
|
|
||||||
{},
|
|
||||||
bodyshop && bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
|
|
||||||
const { Enhanced_Payroll } = useTreatments(
|
const { treatments: {Simple_Inventory} } = useSplitTreatments({
|
||||||
["Enhanced_Payroll"],
|
attributes: {},
|
||||||
{},
|
names: ["Simple_Inventory"],
|
||||||
bodyshop.imexshopid
|
splitKey: bodyshop && bodyshop.imexshopid,
|
||||||
);
|
});
|
||||||
|
|
||||||
|
|
||||||
const columns = (remove) => {
|
const columns = (remove) => {
|
||||||
return [
|
return [
|
||||||
@@ -102,7 +97,6 @@ export function BillEnterModalLinesComponent({
|
|||||||
line_desc: opt.line_desc,
|
line_desc: opt.line_desc,
|
||||||
quantity: opt.part_qty || 1,
|
quantity: opt.part_qty || 1,
|
||||||
actual_price: opt.cost,
|
actual_price: opt.cost,
|
||||||
original_actual_price: opt.cost,
|
|
||||||
cost_center: opt.part_type
|
cost_center: opt.part_type
|
||||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
||||||
? opt.part_type !== "PAE"
|
? opt.part_type !== "PAE"
|
||||||
@@ -229,43 +223,6 @@ export function BillEnterModalLinesComponent({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
additional: (record, index) => (
|
|
||||||
<Form.Item
|
|
||||||
dependencies={["billlines", record.name, "actual_price"]}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
{() => {
|
|
||||||
const billLine = getFieldValue(["billlines", record.name]);
|
|
||||||
const jobLine = lineData.find(
|
|
||||||
(line) => line.id === billLine?.joblineid
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!billEdit &&
|
|
||||||
billLine &&
|
|
||||||
jobLine &&
|
|
||||||
billLine?.actual_price !== jobLine?.act_price
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<Space size="small">
|
|
||||||
<Form.Item
|
|
||||||
noStyle
|
|
||||||
label={t("joblines.fields.create_ppc")}
|
|
||||||
key={`${index}ppc`}
|
|
||||||
valuePropName="checked"
|
|
||||||
name={[record.name, "create_ppc"]}
|
|
||||||
>
|
|
||||||
<Checkbox />
|
|
||||||
</Form.Item>
|
|
||||||
{t("joblines.fields.create_ppc")}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.actual_cost"),
|
title: t("billlines.fields.actual_cost"),
|
||||||
@@ -407,7 +364,7 @@ export function BillEnterModalLinesComponent({
|
|||||||
},
|
},
|
||||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||||
additional: (record, index) => (
|
additional: (record, index) => (
|
||||||
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
|
||||||
{() => {
|
{() => {
|
||||||
const price = getFieldValue([
|
const price = getFieldValue([
|
||||||
"billlines",
|
"billlines",
|
||||||
@@ -422,31 +379,12 @@ export function BillEnterModalLinesComponent({
|
|||||||
"rate",
|
"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"]))
|
if (getFieldValue(["billlines", record.name, "deductedfromlbr"]))
|
||||||
return (
|
return (
|
||||||
<div>
|
<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
|
<Form.Item
|
||||||
label={t("joblines.fields.mod_lbr_ty")}
|
label={t("joblines.fields.mod_lbr_ty")}
|
||||||
key={`${index}modlbrty`}
|
key={`${index}modlbrty`}
|
||||||
initialValue={jobline ? jobline.mod_lbr_ty : null}
|
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -500,44 +438,22 @@ export function BillEnterModalLinesComponent({
|
|||||||
</Select.Option>
|
</Select.Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{Enhanced_Payroll.treatment === "on" ? (
|
<Form.Item
|
||||||
<Form.Item
|
label={t("jobs.labels.adjustmentrate")}
|
||||||
label={t("billlines.labels.mod_lbr_adjustment")}
|
name={[record.name, "lbr_adjustment", "rate"]}
|
||||||
name={[record.name, "lbr_adjustment", "mod_lb_hrs"]}
|
initialValue={bodyshop.default_adjustment_rate}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<InputNumber
|
<InputNumber precision={2} min={0.01} />
|
||||||
precision={5}
|
</Form.Item>
|
||||||
min={0.01}
|
{price &&
|
||||||
max={jobline ? jobline.mod_lb_hrs : 0}
|
adjustmentRate &&
|
||||||
/>
|
`${(price / adjustmentRate).toFixed(1)} hrs`}
|
||||||
</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>
|
</div>
|
||||||
);
|
);
|
||||||
return <></>;
|
return <></>;
|
||||||
@@ -545,21 +461,22 @@ export function BillEnterModalLinesComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// title: t("billlines.fields.federal_tax_applicable"),
|
title: t("billlines.fields.federal_tax_applicable"),
|
||||||
// dataIndex: "applicable_taxes.federal",
|
dataIndex: "applicable_taxes.federal",
|
||||||
// editable: true,
|
editable: true,
|
||||||
|
|
||||||
// formItemProps: (field) => {
|
formItemProps: (field) => {
|
||||||
// return {
|
return {
|
||||||
// key: `${field.index}fedtax`,
|
key: `${field.index}fedtax`,
|
||||||
// valuePropName: "checked",
|
valuePropName: "checked",
|
||||||
// // initialValue: true,
|
initialValue:
|
||||||
// name: [field.name, "applicable_taxes", "federal"],
|
form.getFieldValue("federal_tax_exempt") === true ? false : true,
|
||||||
// };
|
name: [field.name, "applicable_taxes", "federal"],
|
||||||
// },
|
};
|
||||||
// formInput: (record, index) => <Switch disabled={disabled} />,
|
},
|
||||||
// },
|
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("billlines.fields.state_tax_applicable"),
|
title: t("billlines.fields.state_tax_applicable"),
|
||||||
dataIndex: "applicable_taxes.state",
|
dataIndex: "applicable_taxes.state",
|
||||||
@@ -574,20 +491,20 @@ export function BillEnterModalLinesComponent({
|
|||||||
},
|
},
|
||||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// title: t("billlines.fields.local_tax_applicable"),
|
title: t("billlines.fields.local_tax_applicable"),
|
||||||
// dataIndex: "applicable_taxes.local",
|
dataIndex: "applicable_taxes.local",
|
||||||
// editable: true,
|
editable: true,
|
||||||
|
|
||||||
// formItemProps: (field) => {
|
formItemProps: (field) => {
|
||||||
// return {
|
return {
|
||||||
// key: `${field.index}localtax`,
|
key: `${field.index}localtax`,
|
||||||
// valuePropName: "checked",
|
valuePropName: "checked",
|
||||||
// name: [field.name, "applicable_taxes", "local"],
|
name: [field.name, "applicable_taxes", "local"],
|
||||||
// };
|
};
|
||||||
// },
|
},
|
||||||
// formInput: (record, index) => <Switch disabled={disabled} />,
|
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
|
|
||||||
@@ -712,7 +629,7 @@ const EditableCell = ({
|
|||||||
if (additional)
|
if (additional)
|
||||||
return (
|
return (
|
||||||
<td {...restProps}>
|
<td {...restProps}>
|
||||||
<div size="small">
|
<Space size="small">
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={dataIndex}
|
name={dataIndex}
|
||||||
labelCol={{ span: 0 }}
|
labelCol={{ span: 0 }}
|
||||||
@@ -721,7 +638,7 @@ const EditableCell = ({
|
|||||||
{(formInput && formInput(record, record.name)) || children}
|
{(formInput && formInput(record, record.name)) || children}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{additional && additional(record, record.name)}
|
{additional && additional(record, record.name)}
|
||||||
</div>
|
</Space>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
if (wrapper)
|
if (wrapper)
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ export const CalculateBillTotal = (invoice) => {
|
|||||||
}).multiply(i.quantity || 1);
|
}).multiply(i.quantity || 1);
|
||||||
|
|
||||||
subtotal = subtotal.add(itemTotal);
|
subtotal = subtotal.add(itemTotal);
|
||||||
if (i.applicable_taxes?.federal) {
|
if (i.applicable_taxes.federal) {
|
||||||
federalTax = federalTax.add(
|
federalTax = federalTax.add(
|
||||||
itemTotal.percentage(federal_tax_rate || 0)
|
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));
|
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));
|
localTax = localTax.add(itemTotal.percentage(local_tax_rate || 0));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ const BillLineSearchSelect = (
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
showSearch
|
showSearch
|
||||||
dropdownMatchSelectWidth={false}
|
popupMatchSelectWidth={false}
|
||||||
|
optionLabelProp={"name"}
|
||||||
// optionFilterProp="line_desc"
|
// optionFilterProp="line_desc"
|
||||||
filterOption={(inputValue, option) => {
|
filterOption={(inputValue, option) => {
|
||||||
return (
|
return (
|
||||||
@@ -57,18 +58,15 @@ const BillLineSearchSelect = (
|
|||||||
style={{
|
style={{
|
||||||
...(item.removed ? { textDecoration: "line-through" } : {}),
|
...(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>
|
<span>
|
||||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
|
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
|
||||||
</span>
|
</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" }}>
|
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||||
{item.act_price
|
{item.act_price
|
||||||
? `$${item.act_price && item.act_price.toFixed(2)}`
|
? `$${item.act_price && item.act_price.toFixed(2)}`
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { Button, Space } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
|
||||||
|
export default function BillPrintButton({ billid }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const Templates = TemplateList("job_special");
|
||||||
|
|
||||||
|
const submitHandler = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await GenerateDocument(
|
||||||
|
{
|
||||||
|
name: Templates.parts_invoice_label_single.key,
|
||||||
|
variables: {
|
||||||
|
id: billid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
"p"
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Warning: Error generating a document.");
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space wrap>
|
||||||
|
<Button loading={loading} onClick={submitHandler}>
|
||||||
|
{t("bills.labels.printlabels")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { FileAddFilled } from "@ant-design/icons";
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, notification, Tooltip } from "antd";
|
import { Button, notification, Tooltip } from "antd";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import moment from "moment";
|
import dayjs from "./../../utils/day";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -36,7 +36,6 @@ export function BilllineAddInventory({
|
|||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { billid } = queryString.parse(useLocation().search);
|
const { billid } = queryString.parse(useLocation().search);
|
||||||
|
|
||||||
const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
|
const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
|
||||||
|
|
||||||
const addToInventory = async () => {
|
const addToInventory = async () => {
|
||||||
@@ -50,7 +49,7 @@ export function BilllineAddInventory({
|
|||||||
jobid: jobid,
|
jobid: jobid,
|
||||||
isinhouse: true,
|
isinhouse: true,
|
||||||
is_credit_memo: true,
|
is_credit_memo: true,
|
||||||
date: moment().format("YYYY-MM-DD"),
|
date: dayjs().format("YYYY-MM-DD"),
|
||||||
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate,
|
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate,
|
||||||
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate,
|
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate,
|
||||||
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate,
|
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate,
|
||||||
@@ -92,7 +91,7 @@ export function BilllineAddInventory({
|
|||||||
pol: {
|
pol: {
|
||||||
returnfrombill: billid,
|
returnfrombill: billid,
|
||||||
vendorid: bodyshop.inhousevendorid,
|
vendorid: bodyshop.inhousevendorid,
|
||||||
deliver_by: moment().format("YYYY-MM-DD"),
|
deliver_by: dayjs().format("YYYY-MM-DD"),
|
||||||
parts_order_lines: {
|
parts_order_lines: {
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||||||
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
|
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
|
||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { Table, Input } from "antd";
|
import { Table, Input } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
@@ -10,7 +10,7 @@ import AlertComponent from "../alert/alert.component";
|
|||||||
|
|
||||||
export default function BillsVendorsList() {
|
export default function BillsVendorsList() {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const history = useHistory();
|
const history = useNavigate();
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS, {
|
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
|
|||||||
@@ -1,54 +1,64 @@
|
|||||||
import { HomeFilled } from "@ant-design/icons";
|
import {HomeFilled} from "@ant-design/icons";
|
||||||
import { Breadcrumb, Row, Col } from "antd";
|
import {Breadcrumb, Col, Row} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import {Link} from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
|
import {selectBreadcrumbs} from "../../redux/application/application.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||||
import GlobalSearch from "../global-search/global-search.component";
|
import GlobalSearch from "../global-search/global-search.component";
|
||||||
import GlobalSearchOs from "../global-search/global-search-os.component";
|
import GlobalSearchOs from "../global-search/global-search-os.component";
|
||||||
import "./breadcrumbs.styles.scss";
|
import "./breadcrumbs.styles.scss";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
breadcrumbs: selectBreadcrumbs,
|
breadcrumbs: selectBreadcrumbs,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
|
|
||||||
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
export function BreadCrumbs({breadcrumbs, bodyshop}) {
|
||||||
const { OpenSearch } = useTreatments(
|
|
||||||
["OpenSearch"],
|
|
||||||
{},
|
|
||||||
bodyshop && bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
const {treatments: {OpenSearch}} = useSplitTreatments({
|
||||||
<Row className="breadcrumb-container">
|
attributes: {},
|
||||||
<Col xs={24} sm={24} md={16}>
|
names: ["OpenSearch"],
|
||||||
<Breadcrumb separator=">">
|
splitKey: bodyshop && bodyshop.imexshopid,
|
||||||
<Breadcrumb.Item>
|
});
|
||||||
<Link to={`/manage`}>
|
// TODO - Client Update - Technically key is not doing anything here
|
||||||
<HomeFilled />{" "}
|
return (
|
||||||
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
|
<Row className="breadcrumb-container">
|
||||||
""}
|
<Col xs={24} sm={24} md={16}>
|
||||||
</Link>
|
<Breadcrumb
|
||||||
</Breadcrumb.Item>
|
separator=">"
|
||||||
{breadcrumbs.map((item) =>
|
items={[
|
||||||
item.link ? (
|
{
|
||||||
<Breadcrumb.Item key={item.label}>
|
key: "home",
|
||||||
<Link to={item.link}>{item.label} </Link>
|
title: (
|
||||||
</Breadcrumb.Item>
|
<Link to={`/manage/`}>
|
||||||
) : (
|
<HomeFilled/>{" "}
|
||||||
<Breadcrumb.Item key={item.label}>{item.label}</Breadcrumb.Item>
|
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
|
||||||
)
|
""}
|
||||||
)}
|
</Link>
|
||||||
</Breadcrumb>
|
),
|
||||||
</Col>
|
},
|
||||||
<Col xs={24} sm={24} md={8}>
|
...breadcrumbs.map((item) =>
|
||||||
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />}
|
item.link
|
||||||
</Col>
|
? {
|
||||||
</Row>
|
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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(BreadCrumbs);
|
export default connect(mapStateToProps, null)(BreadCrumbs);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function ContractsFindModalContainer({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { visible } = caBcEtfTableModal;
|
const { open } = caBcEtfTableModal;
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const EtfTemplate = TemplateList("special").ca_bc_etf_table;
|
const EtfTemplate = TemplateList("special").ca_bc_etf_table;
|
||||||
@@ -63,14 +63,14 @@ export function ContractsFindModalContainer({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (open) {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
}
|
}
|
||||||
}, [visible, form]);
|
}, [open, form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
open={open}
|
||||||
width="70%"
|
width="70%"
|
||||||
title={t("payments.labels.findermodal")}
|
title={t("payments.labels.findermodal")}
|
||||||
onCancel={() => toggleModalVisible()}
|
onCancel={() => toggleModalVisible()}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
|||||||
<Popover
|
<Popover
|
||||||
destroyTooltipOnHide
|
destroyTooltipOnHide
|
||||||
content={popContent}
|
content={popContent}
|
||||||
visible={visibility}
|
open={visibility}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
notification,
|
notification,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -117,7 +117,7 @@ const CardPaymentModalComponent = ({
|
|||||||
payer: t("payments.labels.customer"),
|
payer: t("payments.labels.customer"),
|
||||||
type: values.paymentResponse.cardbrand,
|
type: values.paymentResponse.cardbrand,
|
||||||
jobid: payment.jobid,
|
jobid: payment.jobid,
|
||||||
date: moment(Date.now()),
|
date: dayjs(Date.now()),
|
||||||
payment_responses: {
|
payment_responses: {
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ function CardPaymentModalContainer({
|
|||||||
toggleModalVisible,
|
toggleModalVisible,
|
||||||
bodyshop,
|
bodyshop,
|
||||||
}) {
|
}) {
|
||||||
const { visible } = cardPaymentModal;
|
const { open } = cardPaymentModal;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
@@ -35,7 +35,7 @@ function CardPaymentModalContainer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={visible}
|
open={open}
|
||||||
onOk={handleOK}
|
onOk={handleOK}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
footer={[
|
footer={[
|
||||||
|
|||||||
@@ -4,20 +4,11 @@ import { Button, notification, Space } from "antd";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { messaging, requestForToken } from "../../firebase/firebase.utils";
|
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 FcmHandler from "../../utils/fcm-handler";
|
||||||
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
||||||
import "./chat-affix.styles.scss";
|
import "./chat-affix.styles.scss";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
chatVisible: selectChatVisible,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -28,7 +19,7 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
|||||||
try {
|
try {
|
||||||
const r = await axios.post("/notifications/subscribe", {
|
const r = await axios.post("/notifications/subscribe", {
|
||||||
fcm_tokens: await getToken(messaging, {
|
fcm_tokens: await getToken(messaging, {
|
||||||
vapidKey: process.env.REACT_APP_FIREBASE_PUBLIC_VAPID_KEY,
|
vapidKey: import.meta.env.VITE_APP_FIREBASE_PUBLIC_VAPID_KEY,
|
||||||
}),
|
}),
|
||||||
type: "messaging",
|
type: "messaging",
|
||||||
imexshopid: bodyshop.imexshopid,
|
imexshopid: bodyshop.imexshopid,
|
||||||
@@ -36,35 +27,34 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
|||||||
console.log("FCM Topic Subscription", r.data);
|
console.log("FCM Topic Subscription", r.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(
|
console.log(
|
||||||
"Error attempting to subscribe to messaging topic: ",
|
"Error attempting to subscribe to messaging topic: ",
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
notification.open({
|
notification.open({
|
||||||
type: "warning",
|
type: "warning",
|
||||||
message: t("general.errors.fcm"),
|
message: t("general.errors.fcm"),
|
||||||
btn: (
|
btn: (
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await requestForToken();
|
await requestForToken();
|
||||||
|
SubscribeToTopic();
|
||||||
SubscribeToTopic();
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{t("general.actions.tryagain")}
|
||||||
{t("general.actions.tryagain")}
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
onClick={() => {
|
||||||
onClick={() => {
|
const win = window.open(
|
||||||
const win = window.open(
|
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
|
||||||
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
|
"_blank"
|
||||||
"_blank"
|
);
|
||||||
);
|
win.focus();
|
||||||
win.focus();
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{t("general.labels.help")}
|
||||||
{t("general.labels.help")}
|
</Button>
|
||||||
</Button>
|
</Space>
|
||||||
</Space>
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -81,16 +71,16 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
|||||||
payload: (payload && payload.data && payload.data.data) || payload.data,
|
payload: (payload && payload.data && payload.data.data) || payload.data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let stopMessageListenr, channel;
|
let stopMessageListener, channel;
|
||||||
try {
|
try {
|
||||||
stopMessageListenr = onMessage(messaging, handleMessage);
|
stopMessageListener = onMessage(messaging, handleMessage);
|
||||||
channel = new BroadcastChannel("imex-sw-messages");
|
channel = new BroadcastChannel("imex-sw-messages");
|
||||||
channel.addEventListener("message", handleMessage);
|
channel.addEventListener("message", handleMessage);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Unable to set event listeners.");
|
console.log("Unable to set event listeners.");
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
stopMessageListenr && stopMessageListenr();
|
stopMessageListener && stopMessageListener();
|
||||||
channel && channel.removeEventListener("message", handleMessage);
|
channel && channel.removeEventListener("message", handleMessage);
|
||||||
};
|
};
|
||||||
}, [client]);
|
}, [client]);
|
||||||
@@ -98,9 +88,10 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
|||||||
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
|
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
||||||
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
|
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(mapStateToProps, null)(ChatAffixContainer);
|
|
||||||
|
export default ChatAffixContainer;
|
||||||
@@ -1,120 +1,122 @@
|
|||||||
import { Badge, List, Tag } from "antd";
|
import {Badge, Card, List, Space, Tag} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import {
|
import {AutoSizer, CellMeasurer, CellMeasurerCache, List as VirtualizedList,} from "react-virtualized";
|
||||||
AutoSizer,
|
import {createStructuredSelector} from "reselect";
|
||||||
CellMeasurer,
|
import {setSelectedConversation} from "../../redux/messaging/messaging.actions";
|
||||||
CellMeasurerCache,
|
import {selectSelectedConversation} from "../../redux/messaging/messaging.selectors";
|
||||||
List as VirtualizedList,
|
import {TimeAgoFormatter} from "../../utils/DateFormatter";
|
||||||
} 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 PhoneFormatter from "../../utils/PhoneFormatter";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import {OwnerNameDisplayFunction} from "../owner-name-display/owner-name-display.component";
|
||||||
|
import _ from "lodash";
|
||||||
import "./chat-conversation-list.styles.scss";
|
import "./chat-conversation-list.styles.scss";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
selectedConversation: selectSelectedConversation,
|
selectedConversation: selectSelectedConversation,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setSelectedConversation: (conversationId) =>
|
setSelectedConversation: (conversationId) =>
|
||||||
dispatch(setSelectedConversation(conversationId)),
|
dispatch(setSelectedConversation(conversationId)),
|
||||||
});
|
});
|
||||||
|
|
||||||
function ChatConversationListComponent({
|
function ChatConversationListComponent({
|
||||||
conversationList,
|
conversationList,
|
||||||
selectedConversation,
|
selectedConversation,
|
||||||
setSelectedConversation,
|
setSelectedConversation,
|
||||||
loadMoreConversations,
|
loadMoreConversations,
|
||||||
}) {
|
}) {
|
||||||
const cache = new CellMeasurerCache({
|
const cache = new CellMeasurerCache({
|
||||||
fixedWidth: true,
|
fixedWidth: true,
|
||||||
defaultHeight: 60,
|
defaultHeight: 60,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rowRenderer = ({ index, key, style, parent }) => {
|
const rowRenderer = ({index, key, style, parent}) => {
|
||||||
const item = conversationList[index];
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CellMeasurer
|
<div className="chat-list-container">
|
||||||
key={key}
|
<AutoSizer>
|
||||||
cache={cache}
|
{({height, width}) => (
|
||||||
parent={parent}
|
<VirtualizedList
|
||||||
columnIndex={0}
|
height={height}
|
||||||
rowIndex={index}
|
width={width}
|
||||||
>
|
rowCount={conversationList.length}
|
||||||
<List.Item
|
rowHeight={cache.rowHeight}
|
||||||
onClick={() => setSelectedConversation(item.id)}
|
rowRenderer={rowRenderer}
|
||||||
className={`chat-list-item ${
|
onScroll={({scrollTop, scrollHeight, clientHeight}) => {
|
||||||
item.id === selectedConversation
|
if (scrollTop + clientHeight === scrollHeight) {
|
||||||
? "chat-list-selected-conversation"
|
loadMoreConversations();
|
||||||
: null
|
}
|
||||||
}`}
|
}}
|
||||||
style={style}
|
/>
|
||||||
>
|
)}
|
||||||
<div
|
</AutoSizer>
|
||||||
style={{
|
</div>
|
||||||
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(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(ChatConversationListComponent);
|
)(ChatConversationListComponent);
|
||||||
|
|||||||
@@ -1,27 +1,14 @@
|
|||||||
.chat-list-selected-conversation {
|
|
||||||
background-color: rgba(128, 128, 128, 0.2);
|
|
||||||
}
|
|
||||||
.chat-list-container {
|
.chat-list-container {
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: 1px solid gainsboro;
|
border: 1px solid gainsboro;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-list-item {
|
.chat-list-item {
|
||||||
display: flex;
|
.ant-card-head {
|
||||||
flex-direction: row;
|
border: none;
|
||||||
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #ff7a00;
|
color: #ff7a00;
|
||||||
}
|
}
|
||||||
.chat-name {
|
|
||||||
flex: 1;
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
.ro-number-tag {
|
|
||||||
align-self: baseline;
|
|
||||||
}
|
|
||||||
padding: 12px 24px;
|
|
||||||
border-bottom: 1px solid gainsboro;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
|||||||
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
|
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
|
||||||
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
|
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
|
||||||
import ChatLabelComponent from "../chat-label/chat-label.component";
|
import ChatLabelComponent from "../chat-label/chat-label.component";
|
||||||
|
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
|
||||||
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
|
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
|
||||||
|
|
||||||
export default function ChatConversationTitle({ conversation }) {
|
export default function ChatConversationTitle({ conversation }) {
|
||||||
@@ -13,6 +14,7 @@ export default function ChatConversationTitle({ conversation }) {
|
|||||||
{conversation && conversation.phone_num}
|
{conversation && conversation.phone_num}
|
||||||
</PhoneNumberFormatter>
|
</PhoneNumberFormatter>
|
||||||
<ChatLabelComponent conversation={conversation} />
|
<ChatLabelComponent conversation={conversation} />
|
||||||
|
<ChatPrintButton conversation={conversation} />
|
||||||
<ChatConversationTitleTags
|
<ChatConversationTitleTags
|
||||||
jobConversations={
|
jobConversations={
|
||||||
(conversation && conversation.job_conversations) || []
|
(conversation && conversation.job_conversations) || []
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function ChatMediaSelector({
|
|||||||
conversation,
|
conversation,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [visible, setVisible] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
|
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
@@ -39,13 +39,13 @@ export function ChatMediaSelector({
|
|||||||
},
|
},
|
||||||
|
|
||||||
skip:
|
skip:
|
||||||
!visible ||
|
!open ||
|
||||||
!conversation.job_conversations ||
|
!conversation.job_conversations ||
|
||||||
conversation.job_conversations.length === 0,
|
conversation.job_conversations.length === 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleVisibleChange = (visible) => {
|
const handleVisibleChange = (change) => {
|
||||||
setVisible(visible);
|
setOpen(change);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -65,7 +65,7 @@ export function ChatMediaSelector({
|
|||||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{bodyshop.uselocalmediaserver && visible && (
|
{bodyshop.uselocalmediaserver && open && (
|
||||||
<JobDocumentsLocalGalleryExternal
|
<JobDocumentsLocalGalleryExternal
|
||||||
externalMediaState={[selectedMedia, setSelectedMedia]}
|
externalMediaState={[selectedMedia, setSelectedMedia]}
|
||||||
jobId={
|
jobId={
|
||||||
@@ -88,8 +88,8 @@ export function ChatMediaSelector({
|
|||||||
}
|
}
|
||||||
title={t("messaging.labels.selectmedia")}
|
title={t("messaging.labels.selectmedia")}
|
||||||
trigger="click"
|
trigger="click"
|
||||||
visible={visible}
|
open={open}
|
||||||
onVisibleChange={handleVisibleChange}
|
onOpenChange={handleVisibleChange}
|
||||||
>
|
>
|
||||||
<Badge count={selectedMedia.filter((s) => s.isSelected).length}>
|
<Badge count={selectedMedia.filter((s) => s.isSelected).length}>
|
||||||
<PictureFilled style={{ margin: "0 .5rem" }} />
|
<PictureFilled style={{ margin: "0 .5rem" }} />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import Icon from "@ant-design/icons";
|
import Icon from "@ant-design/icons";
|
||||||
import { Tooltip } from "antd";
|
import { Tooltip } from "antd";
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { MdDone, MdDoneAll } from "react-icons/md";
|
import { MdDone, MdDoneAll } from "react-icons/md";
|
||||||
import {
|
import {
|
||||||
@@ -52,7 +52,7 @@ export default function ChatMessageListComponent({ messages }) {
|
|||||||
<div style={{ fontSize: 10 }}>
|
<div style={{ fontSize: 10 }}>
|
||||||
{i18n.t("messaging.labels.sentby", {
|
{i18n.t("messaging.labels.sentby", {
|
||||||
by: messages[index].userid,
|
by: messages[index].userid,
|
||||||
time: moment(messages[index].created_at).format(
|
time: dayjs(messages[index].created_at).format(
|
||||||
"MM/DD/YYYY @ hh:mm a"
|
"MM/DD/YYYY @ hh:mm a"
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PlusCircleOutlined } from "@ant-design/icons";
|
import { PlusCircleOutlined } from "@ant-design/icons";
|
||||||
import { Dropdown, Menu } from "antd";
|
import { Dropdown } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -16,19 +16,16 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export function ChatPresetsComponent({ bodyshop, setMessage, className }) {
|
export function ChatPresetsComponent({ bodyshop, setMessage, className }) {
|
||||||
const menu = (
|
|
||||||
<Menu>
|
const items = bodyshop.md_messaging_presets.map((i, idx) => ({
|
||||||
{bodyshop.md_messaging_presets.map((i, idx) => (
|
key: idx,
|
||||||
<Menu.Item onClick={() => setMessage(i.text)} key={idx}>
|
label: (i.label),
|
||||||
{i.label}
|
onClick: () => setMessage(i.text),
|
||||||
</Menu.Item>
|
}));
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<Dropdown trigger={["click"]} overlay={menu}>
|
<Dropdown trigger={["click"]} menu={{items}}>
|
||||||
<PlusCircleOutlined />
|
<PlusCircleOutlined />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
|
||||||
|
import { Space, Spin } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||||
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export function ChatPrintButton({ conversation }) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
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')}/>
|
||||||
|
{loading && <Spin />}
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatPrintButton);
|
||||||
@@ -9,17 +9,17 @@ export default function ChatTagRoComponent({
|
|||||||
loading,
|
loading,
|
||||||
handleSearch,
|
handleSearch,
|
||||||
handleInsertTag,
|
handleInsertTag,
|
||||||
setVisible,
|
setOpen,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space flex>
|
<Space>
|
||||||
<div style={{ width: "15rem" }}>
|
<div style={{ width: "15rem" }}>
|
||||||
<Select
|
<Select
|
||||||
showSearch
|
showSearch
|
||||||
autoFocus
|
autoFocus
|
||||||
dropdownMatchSelectWidth
|
popupMatchSelectWidth
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
filterOption={false}
|
filterOption={false}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
@@ -38,7 +38,7 @@ export default function ChatTagRoComponent({
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<LoadingOutlined />
|
<LoadingOutlined />
|
||||||
) : (
|
) : (
|
||||||
<CloseCircleOutlined onClick={() => setVisible(false)} />
|
<CloseCircleOutlined onClick={() => setOpen(false)} />
|
||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import ChatTagRo from "./chat-tag-ro.component";
|
|||||||
|
|
||||||
export default function ChatTagRoContainer({ conversation }) {
|
export default function ChatTagRoContainer({ conversation }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [visible, setVisible] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const [loadRo, { loading, data }] = useLazyQuery(SEARCH_FOR_JOBS);
|
const [loadRo, { loading, data }] = useLazyQuery(SEARCH_FOR_JOBS);
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export default function ChatTagRoContainer({ conversation }) {
|
|||||||
const handleInsertTag = (value, option) => {
|
const handleInsertTag = (value, option) => {
|
||||||
logImEXEvent("messaging_add_job_tag");
|
logImEXEvent("messaging_add_job_tag");
|
||||||
insertTag({ variables: { jobId: option.key } });
|
insertTag({ variables: { jobId: option.key } });
|
||||||
setVisible(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const existingJobTags =
|
const existingJobTags =
|
||||||
@@ -47,16 +47,16 @@ export default function ChatTagRoContainer({ conversation }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{visible ? (
|
{open ? (
|
||||||
<ChatTagRo
|
<ChatTagRo
|
||||||
loading={loading}
|
loading={loading}
|
||||||
roOptions={roOptions}
|
roOptions={roOptions}
|
||||||
handleSearch={handleSearch}
|
handleSearch={handleSearch}
|
||||||
handleInsertTag={handleInsertTag}
|
handleInsertTag={handleInsertTag}
|
||||||
setVisible={setVisible}
|
setOpen={setOpen}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Tag onClick={() => setVisible(true)}>
|
<Tag onClick={() => setOpen(true)}>
|
||||||
<PlusOutlined />
|
<PlusOutlined />
|
||||||
{t("messaging.actions.link")}
|
{t("messaging.actions.link")}
|
||||||
</Tag>
|
</Tag>
|
||||||
|
|||||||
@@ -35,6 +35,15 @@ export default function ContractsCarsComponent({
|
|||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
render: (text, record) => <div>{t(record.status)}</div>,
|
render: (text, record) => <div>{t(record.status)}</div>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("courtesycars.fields.readiness"),
|
||||||
|
dataIndex: "readiness",
|
||||||
|
key: "readiness",
|
||||||
|
sorter: (a, b) => alphaSort(a.readiness, b.readiness),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => t(record.readiness),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("courtesycars.fields.year"),
|
title: t("courtesycars.fields.year"),
|
||||||
dataIndex: "year",
|
dataIndex: "year",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
import { useQuery } from "@apollo/client";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { QUERY_AVAILABLE_CC } from "../../graphql/courtesy-car.queries";
|
import { QUERY_AVAILABLE_CC } from "../../graphql/courtesy-car.queries";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
@@ -7,7 +7,7 @@ import ContractCarsComponent from "./contract-cars.component";
|
|||||||
|
|
||||||
export default function ContractCarsContainer({ selectedCarState, form }) {
|
export default function ContractCarsContainer({ selectedCarState, form }) {
|
||||||
const { loading, error, data } = useQuery(QUERY_AVAILABLE_CC, {
|
const { loading, error, data } = useQuery(QUERY_AVAILABLE_CC, {
|
||||||
variables: { today: moment().format("YYYY-MM-DD") },
|
variables: { today: dayjs().format("YYYY-MM-DD") },
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { INSERT_NEW_JOB } from "../../graphql/jobs.queries";
|
import { INSERT_NEW_JOB } from "../../graphql/jobs.queries";
|
||||||
import {
|
import {
|
||||||
@@ -38,17 +38,17 @@ export function ContractConvertToRo({
|
|||||||
disabled,
|
disabled,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [visible, setVisible] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertJob] = useMutation(INSERT_NEW_JOB);
|
const [insertJob] = useMutation(INSERT_NEW_JOB);
|
||||||
const history = useHistory();
|
const history = useNavigate();
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const contractLength = moment(contract.actualreturn).diff(
|
const contractLength = dayjs(contract.actualreturn).diff(
|
||||||
moment(contract.start),
|
dayjs(contract.start),
|
||||||
"days"
|
"day"
|
||||||
);
|
);
|
||||||
const billingLines = [];
|
const billingLines = [];
|
||||||
if (contractLength > 0)
|
if (contractLength > 0)
|
||||||
@@ -306,7 +306,7 @@ export function ContractConvertToRo({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setVisible(false);
|
setOpen(false);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -380,7 +380,7 @@ export function ContractConvertToRo({
|
|||||||
<Button type="primary" htmlType="submit" loading={loading}>
|
<Button type="primary" htmlType="submit" loading={loading}>
|
||||||
{t("contracts.actions.convertoro")}
|
{t("contracts.actions.convertoro")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={() => setVisible(false)}>
|
<Button onClick={() => setOpen(false)}>
|
||||||
{t("general.actions.close")}
|
{t("general.actions.close")}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -390,9 +390,9 @@ export function ContractConvertToRo({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Popover content={popContent} visible={visible}>
|
<Popover content={popContent} open={open}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setVisible(true)}
|
onClick={() => setOpen(true)}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={!contract.dailyrate || !contract.actualreturn || disabled}
|
disabled={!contract.dailyrate || !contract.actualreturn || disabled}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { WarningFilled } from "@ant-design/icons";
|
import { WarningFilled } from "@ant-design/icons";
|
||||||
import { Form, Input, InputNumber, Space } from "antd";
|
import { Form, Input, InputNumber, Space } from "antd";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
@@ -96,8 +96,8 @@ export default function ContractFormComponent({
|
|||||||
const dueForService =
|
const dueForService =
|
||||||
selectedCar &&
|
selectedCar &&
|
||||||
selectedCar.nextservicedate &&
|
selectedCar.nextservicedate &&
|
||||||
moment(selectedCar.nextservicedate).isBefore(
|
dayjs(selectedCar.nextservicedate).isBefore(
|
||||||
moment(form.getFieldValue("scheduledreturn"))
|
dayjs(form.getFieldValue("scheduledreturn"))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mileageOver || dueForService)
|
if (mileageOver || dueForService)
|
||||||
@@ -190,9 +190,9 @@ export default function ContractFormComponent({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
const dlExpiresBeforeReturn = moment(
|
const dlExpiresBeforeReturn = dayjs(
|
||||||
form.getFieldValue("driver_dlexpiry")
|
form.getFieldValue("driver_dlexpiry")
|
||||||
).isBefore(moment(form.getFieldValue("scheduledreturn")));
|
).isBefore(dayjs(form.getFieldValue("scheduledreturn")));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Button, Input, Modal, Typography } from "antd";
|
import { Button, Input, Modal, Typography } from "antd";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import aamva from "../../utils/aamva";
|
import aamva from "../../utils/aamva";
|
||||||
@@ -26,8 +26,8 @@ export default function ContractLicenseDecodeButton({ form }) {
|
|||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
driver_dlnumber: decodedBarcode.dl,
|
driver_dlnumber: decodedBarcode.dl,
|
||||||
driver_dlexpiry: moment(
|
driver_dlexpiry: dayjs(
|
||||||
`20${decodedBarcode.expiration_date}${moment(
|
`20${decodedBarcode.expiration_date}${dayjs(
|
||||||
decodedBarcode.birthday
|
decodedBarcode.birthday
|
||||||
).format("DD")}`
|
).format("DD")}`
|
||||||
),
|
),
|
||||||
@@ -38,7 +38,7 @@ export default function ContractLicenseDecodeButton({ form }) {
|
|||||||
driver_city: decodedBarcode.city,
|
driver_city: decodedBarcode.city,
|
||||||
driver_state: decodedBarcode.state,
|
driver_state: decodedBarcode.state,
|
||||||
driver_zip: decodedBarcode.postal_code,
|
driver_zip: decodedBarcode.postal_code,
|
||||||
driver_dob: moment(decodedBarcode.birthday),
|
driver_dob: dayjs(decodedBarcode.birthday),
|
||||||
};
|
};
|
||||||
|
|
||||||
form.setFieldsValue(values);
|
form.setFieldsValue(values);
|
||||||
@@ -55,7 +55,7 @@ export default function ContractLicenseDecodeButton({ form }) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Modal
|
<Modal
|
||||||
visible={modalVisible}
|
open={modalVisible}
|
||||||
okText={t("contracts.actions.senddltoform")}
|
okText={t("contracts.actions.senddltoform")}
|
||||||
onOk={handleInsertForm}
|
onOk={handleInsertForm}
|
||||||
okButtonProps={{ disabled: !!!decodedBarcode }}
|
okButtonProps={{ disabled: !!!decodedBarcode }}
|
||||||
@@ -94,14 +94,14 @@ export default function ContractLicenseDecodeButton({ form }) {
|
|||||||
{decodedBarcode.address}
|
{decodedBarcode.address}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel label={t("contracts.fields.driver_dlexpiry")}>
|
<DataLabel label={t("contracts.fields.driver_dlexpiry")}>
|
||||||
{moment(
|
{dayjs(
|
||||||
`20${decodedBarcode.expiration_date}${moment(
|
`20${decodedBarcode.expiration_date}${dayjs(
|
||||||
decodedBarcode.birthday
|
decodedBarcode.birthday
|
||||||
).format("DD")}`
|
).format("DD")}`
|
||||||
).format("MM/DD/YYYY")}
|
).format("MM/DD/YYYY")}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<DataLabel label={t("contracts.fields.driver_dob")}>
|
<DataLabel label={t("contracts.fields.driver_dob")}>
|
||||||
{moment(decodedBarcode.birthday).format("MM/DD/YYYY")}
|
{dayjs(decodedBarcode.birthday).format("MM/DD/YYYY")}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
<div>
|
<div>
|
||||||
<Typography.Title level={4}>
|
<Typography.Title level={4}>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function ContractsFindModalContainer({
|
|||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { visible } = contractFinderModal;
|
const { open } = contractFinderModal;
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
@@ -52,14 +52,14 @@ export function ContractsFindModalContainer({
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (open) {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
}
|
}
|
||||||
}, [visible, form]);
|
}, [open, form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
open={open}
|
||||||
width="70%"
|
width="70%"
|
||||||
title={t("contracts.labels.findermodal")}
|
title={t("contracts.labels.findermodal")}
|
||||||
onCancel={() => toggleModalVisible()}
|
onCancel={() => toggleModalVisible()}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { Button, Card, Input, Space, Table, Typography } from "antd";
|
|||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
import { Link, useNavigate, useLocation } from "react-router-dom";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container";
|
import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container";
|
||||||
|
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
@@ -39,7 +39,7 @@ export function ContractsList({
|
|||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
filteredInfo: { text: "" },
|
filteredInfo: { text: "" },
|
||||||
});
|
});
|
||||||
const history = useHistory();
|
const history = useNavigate();
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const { page } = search;
|
const { page } = search;
|
||||||
|
|
||||||
@@ -152,8 +152,8 @@ export function ContractsList({
|
|||||||
render: (text, record) =>
|
render: (text, record) =>
|
||||||
(record.actualreturn &&
|
(record.actualreturn &&
|
||||||
record.start &&
|
record.start &&
|
||||||
`${moment(record.actualreturn)
|
`${dayjs(record.actualreturn)
|
||||||
.diff(moment(record.start), "days", true)
|
.diff(dayjs(record.start), "day", true)
|
||||||
.toFixed(1)} days`) ||
|
.toFixed(1)} days`) ||
|
||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
@@ -164,7 +164,7 @@ export function ContractsList({
|
|||||||
search.page = pagination.current;
|
search.page = pagination.current;
|
||||||
search.sortcolumn = sorter.columnKey;
|
search.sortcolumn = sorter.columnKey;
|
||||||
search.sortorder = sorter.order;
|
search.sortorder = sorter.order;
|
||||||
history.push({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -179,7 +179,7 @@ export function ContractsList({
|
|||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
delete search.search;
|
delete search.search;
|
||||||
history.push({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("general.actions.clear")}
|
{t("general.actions.clear")}
|
||||||
@@ -196,7 +196,7 @@ export function ContractsList({
|
|||||||
placeholder={search.searh || t("general.labels.search")}
|
placeholder={search.searh || t("general.labels.search")}
|
||||||
onSearch={(value) => {
|
onSearch={(value) => {
|
||||||
search.search = value;
|
search.search = value;
|
||||||
history.push({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DownOutlined } from "@ant-design/icons";
|
import { DownOutlined } from "@ant-design/icons";
|
||||||
import { Dropdown, Menu } from "antd";
|
import { Dropdown } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -18,20 +18,16 @@ export function ContractsRatesChangeButton({ disabled, form, bodyshop }) {
|
|||||||
form.setFieldsValue(rate);
|
form.setFieldsValue(rate);
|
||||||
};
|
};
|
||||||
|
|
||||||
const menu = (
|
const menuItems = bodyshop.md_ccc_rates.map((i, idx) => ({
|
||||||
<div>
|
key: idx,
|
||||||
<Menu onClick={handleClick}>
|
label: i.label,
|
||||||
{bodyshop.md_ccc_rates.map((rate, idx) => (
|
value: i,
|
||||||
<Menu.Item value={rate} key={idx}>
|
}));
|
||||||
{rate.label}
|
|
||||||
</Menu.Item>
|
const menu = {items: menuItems, onClick: handleClick};
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown overlay={menu} disabled={disabled}>
|
<Dropdown menu={menu} disabled={disabled}>
|
||||||
<a
|
<a
|
||||||
className="ant-dropdown-link"
|
className="ant-dropdown-link"
|
||||||
href=" #"
|
href=" #"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Card, Table } from "antd";
|
|||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import {pageLimit} from "../../utils/config";
|
import {pageLimit} from "../../utils/config";
|
||||||
@@ -11,9 +11,9 @@ export default function CourtesyCarContractListComponent({
|
|||||||
contracts,
|
contracts,
|
||||||
totalContracts,
|
totalContracts,
|
||||||
}) {
|
}) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search =queryString.parse(useLocation().search);
|
||||||
const { page, sortcolumn, sortorder } = search;
|
const { page, sortcolumn, sortorder } = search;
|
||||||
const history = useHistory();
|
const history = useNavigate();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ export default function CourtesyCarContractListComponent({
|
|||||||
search.page = pagination.current;
|
search.page = pagination.current;
|
||||||
search.sortcolumn = sorter.columnKey;
|
search.sortcolumn = sorter.columnKey;
|
||||||
search.sortorder = sorter.order;
|
search.sortorder = sorter.order;
|
||||||
history.push({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { WarningFilled } from "@ant-design/icons";
|
import { WarningFilled } from "@ant-design/icons";
|
||||||
import { useApolloClient } from "@apollo/client";
|
import { useApolloClient } from "@apollo/client";
|
||||||
import { Button, Form, Input, InputNumber, PageHeader, Space } from "antd";
|
import { Button, Form, Input, InputNumber, Space } from "antd";
|
||||||
import moment from "moment";
|
import {PageHeader} from "@ant-design/pro-layout";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { CHECK_CC_FLEET_NUMBER } from "../../graphql/courtesy-car.queries";
|
import { CHECK_CC_FLEET_NUMBER } from "../../graphql/courtesy-car.queries";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||||
|
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
|
||||||
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||||
//import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
//import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
@@ -213,6 +215,9 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
>
|
>
|
||||||
<CourtesyCarStatus />
|
<CourtesyCarStatus />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label={t("courtesycars.fields.readiness")} name="readiness">
|
||||||
|
<CourtesyCarReadiness />
|
||||||
|
</Form.Item>
|
||||||
<div>
|
<div>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("courtesycars.fields.nextservicekm")}
|
label={t("courtesycars.fields.nextservicekm")}
|
||||||
@@ -227,8 +232,9 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
const nextservicekm = form.getFieldValue("nextservicekm");
|
const nextservicekm = form.getFieldValue("nextservicekm");
|
||||||
const mileageOver =
|
const mileageOver = nextservicekm
|
||||||
nextservicekm && nextservicekm <= form.getFieldValue("mileage");
|
? nextservicekm <= form.getFieldValue("mileage")
|
||||||
|
: false;
|
||||||
if (mileageOver)
|
if (mileageOver)
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||||
@@ -258,7 +264,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
const nextservicedate = form.getFieldValue("nextservicedate");
|
const nextservicedate = form.getFieldValue("nextservicedate");
|
||||||
const dueForService =
|
const dueForService =
|
||||||
nextservicedate &&
|
nextservicedate &&
|
||||||
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
|
dayjs(nextservicedate).endOf("day").isSameOrBefore(dayjs());
|
||||||
|
|
||||||
if (dueForService)
|
if (dueForService)
|
||||||
return (
|
return (
|
||||||
@@ -299,7 +305,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
const expires = form.getFieldValue("registrationexpires");
|
const expires = form.getFieldValue("registrationexpires");
|
||||||
|
|
||||||
const dateover =
|
const dateover =
|
||||||
expires && moment(expires).endOf("day").isBefore(moment());
|
expires && dayjs(expires).endOf("day").isBefore(dayjs());
|
||||||
|
|
||||||
if (dateover)
|
if (dateover)
|
||||||
return (
|
return (
|
||||||
@@ -335,7 +341,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
|||||||
const expires = form.getFieldValue("insuranceexpires");
|
const expires = form.getFieldValue("insuranceexpires");
|
||||||
|
|
||||||
const dateover =
|
const dateover =
|
||||||
expires && moment(expires).endOf("day").isBefore(moment());
|
expires && dayjs(expires).endOf("day").isBefore(dayjs());
|
||||||
|
|
||||||
if (dateover)
|
if (dateover)
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -34,6 +34,32 @@ const CourtesyCarFuelComponent = (props, ref) => {
|
|||||||
step={null}
|
step={null}
|
||||||
style={{ marginLeft: "2rem", marginRight: "2rem" }}
|
style={{ marginLeft: "2rem", marginRight: "2rem" }}
|
||||||
{...props}
|
{...props}
|
||||||
|
tooltip={{
|
||||||
|
formatter: (value) => {
|
||||||
|
switch (value) {
|
||||||
|
case 0:
|
||||||
|
return t("courtesycars.labels.fuel.empty");
|
||||||
|
case 13:
|
||||||
|
return t("courtesycars.labels.fuel.18");
|
||||||
|
case 25:
|
||||||
|
return t("courtesycars.labels.fuel.14");
|
||||||
|
case 38:
|
||||||
|
return t("courtesycars.labels.fuel.38");
|
||||||
|
case 50:
|
||||||
|
return t("courtesycars.labels.fuel.12");
|
||||||
|
case 63:
|
||||||
|
return t("courtesycars.labels.fuel.58");
|
||||||
|
case 75:
|
||||||
|
return t("courtesycars.labels.fuel.34");
|
||||||
|
case 88:
|
||||||
|
return t("courtesycars.labels.fuel.78");
|
||||||
|
case 100:
|
||||||
|
return t("courtesycars.labels.fuel.full");
|
||||||
|
default:
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { Select } from "antd";
|
||||||
|
import React, { forwardRef, useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
const CourtesyCarReadinessComponent = ({ value, onChange }, ref) => {
|
||||||
|
const [option, setOption] = useState(value);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value !== option && onChange) {
|
||||||
|
onChange(option);
|
||||||
|
}
|
||||||
|
}, [value, option, onChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
allowClear
|
||||||
|
ref={ref}
|
||||||
|
value={option}
|
||||||
|
style={{
|
||||||
|
width: 100,
|
||||||
|
}}
|
||||||
|
onChange={setOption}
|
||||||
|
>
|
||||||
|
<Option value="courtesycars.readiness.ready">
|
||||||
|
{t("courtesycars.readiness.ready")}
|
||||||
|
</Option>
|
||||||
|
<Option value="courtesycars.readiness.notready">
|
||||||
|
{t("courtesycars.readiness.notready")}
|
||||||
|
</Option>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default forwardRef(CourtesyCarReadinessComponent);
|
||||||
@@ -7,7 +7,7 @@ import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
|||||||
import { selectCourtesyCarReturn } from "../../redux/modals/modals.selectors";
|
import { selectCourtesyCarReturn } from "../../redux/modals/modals.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CourtesyCarReturnModalComponent from "./courtesy-car-return-modal.component";
|
import CourtesyCarReturnModalComponent from "./courtesy-car-return-modal.component";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import { RETURN_CONTRACT } from "../../graphql/cccontracts.queries";
|
import { RETURN_CONTRACT } from "../../graphql/cccontracts.queries";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ export function CCReturnModalContainer({
|
|||||||
bodyshop,
|
bodyshop,
|
||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { visible, context, actions } = courtesyCarReturnModal;
|
const { open, context, actions } = courtesyCarReturnModal;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [updateContract] = useMutation(RETURN_CONTRACT);
|
const [updateContract] = useMutation(RETURN_CONTRACT);
|
||||||
@@ -64,7 +64,7 @@ export function CCReturnModalContainer({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={t("courtesycars.labels.return")}
|
title={t("courtesycars.labels.return")}
|
||||||
visible={visible}
|
open={open}
|
||||||
onCancel={() => toggleModalVisible()}
|
onCancel={() => toggleModalVisible()}
|
||||||
width={"90%"}
|
width={"90%"}
|
||||||
okText={t("general.actions.save")}
|
okText={t("general.actions.save")}
|
||||||
@@ -74,7 +74,7 @@ export function CCReturnModalContainer({
|
|||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
initialValues={{ fuel: 100, actualreturn: moment(new Date()) }}
|
initialValues={{ fuel: 100, actualreturn: dayjs(new Date()) }}
|
||||||
>
|
>
|
||||||
<CourtesyCarReturnModalComponent />
|
<CourtesyCarReturnModalComponent />
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Input,
|
Input,
|
||||||
Menu,
|
|
||||||
Space,
|
Space,
|
||||||
Table,
|
Table,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -74,10 +73,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
const { nextservicedate, nextservicekm, mileage } = record;
|
const { nextservicedate, nextservicekm, mileage } = record;
|
||||||
|
|
||||||
const mileageOver = nextservicekm <= mileage;
|
const mileageOver = nextservicekm ? nextservicekm <= mileage : false;
|
||||||
|
|
||||||
const dueForService =
|
const dueForService =
|
||||||
nextservicedate && moment(nextservicedate).isBefore(moment());
|
nextservicedate && dayjs(nextservicedate).endOf('day').isSameOrBefore(dayjs());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
@@ -91,6 +90,26 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("courtesycars.fields.readiness"),
|
||||||
|
dataIndex: "readiness",
|
||||||
|
key: "readiness",
|
||||||
|
sorter: (a, b) => alphaSort(a.readiness, b.readiness),
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
text: t("courtesycars.readiness.ready"),
|
||||||
|
value: "courtesycars.readiness.ready",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t("courtesycars.readiness.notready"),
|
||||||
|
value: "courtesycars.readiness.notready",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
onFilter: (value, record) => value.includes(record.readiness),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => t(record.readiness),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("courtesycars.fields.year"),
|
title: t("courtesycars.fields.year"),
|
||||||
dataIndex: "year",
|
dataIndex: "year",
|
||||||
@@ -131,6 +150,36 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "plate" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "plate" && state.sortedInfo.order,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t("courtesycars.fields.fuel"),
|
||||||
|
dataIndex: "fuel",
|
||||||
|
key: "fuel",
|
||||||
|
sorter: (a, b) => alphaSort(a.fuel, b.fuel),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => {
|
||||||
|
switch (record.fuel) {
|
||||||
|
case 100:
|
||||||
|
return t("courtesycars.labels.fuel.full");
|
||||||
|
case 88:
|
||||||
|
return t("courtesycars.labels.fuel.78");
|
||||||
|
case 63:
|
||||||
|
return t("courtesycars.labels.fuel.58");
|
||||||
|
case 50:
|
||||||
|
return t("courtesycars.labels.fuel.12");
|
||||||
|
case 38:
|
||||||
|
return t("courtesycars.labels.fuel.34");
|
||||||
|
case 25:
|
||||||
|
return t("courtesycars.labels.fuel.14");
|
||||||
|
case 13:
|
||||||
|
return t("courtesycars.labels.fuel.18");
|
||||||
|
case 0:
|
||||||
|
return t("courtesycars.labels.fuel.empty");
|
||||||
|
default:
|
||||||
|
return record.fuel;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t("courtesycars.labels.outwith"),
|
title: t("courtesycars.labels.outwith"),
|
||||||
dataIndex: "outwith",
|
dataIndex: "outwith",
|
||||||
@@ -178,6 +227,27 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
|
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
|
||||||
)
|
)
|
||||||
: courtesycars;
|
: 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 (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={t("menus.header.courtesycars")}
|
title={t("menus.header.courtesycars")}
|
||||||
@@ -186,30 +256,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
<Button onClick={() => refetch()}>
|
<Button onClick={() => refetch()}>
|
||||||
<SyncOutlined />
|
<SyncOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
<Dropdown
|
<Dropdown trigger="click" menu={menu}>
|
||||||
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>
|
<Button>{t("general.labels.print")}</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Link to={`/manage/courtesycars/new`}>
|
<Link to={`/manage/courtesycars/new`}>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Button, Card, Table } from "antd";
|
|||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
import { Link, useNavigate, useLocation } from "react-router-dom";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
@@ -17,7 +17,7 @@ export default function CsiResponseListPaginated({
|
|||||||
}) {
|
}) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const { responseid, page, sortcolumn, sortorder } = search;
|
const { responseid, page, sortcolumn, sortorder } = search;
|
||||||
const history = useHistory();
|
const history = useNavigate();
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
filteredInfo: { text: "" },
|
filteredInfo: { text: "" },
|
||||||
@@ -80,18 +80,18 @@ export default function CsiResponseListPaginated({
|
|||||||
search.page = pagination.current;
|
search.page = pagination.current;
|
||||||
search.sortcolumn = sorter.columnKey;
|
search.sortcolumn = sorter.columnKey;
|
||||||
search.sortorder = sorter.order;
|
search.sortorder = sorter.order;
|
||||||
history.push({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnRowClick = (record) => {
|
const handleOnRowClick = (record) => {
|
||||||
if (record) {
|
if (record) {
|
||||||
if (record.id) {
|
if (record.id) {
|
||||||
search.responseid = record.id;
|
search.responseid = record.id;
|
||||||
history.push({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delete search.responseid;
|
delete search.responseid;
|
||||||
history.push({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Card } from "antd";
|
import { Card } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import moment from "moment";
|
import dayjs from "../../../utils/day";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
@@ -27,7 +27,7 @@ export default function DashboardMonthlyEmployeeEfficiency({
|
|||||||
return <DashboardRefreshRequired {...cardProps} />;
|
return <DashboardRefreshRequired {...cardProps} />;
|
||||||
|
|
||||||
const ticketsByDate = _.groupBy(data.monthly_employee_efficiency, (item) =>
|
const ticketsByDate = _.groupBy(data.monthly_employee_efficiency, (item) =>
|
||||||
moment(item.date).format("YYYY-MM-DD")
|
dayjs(item.date).format("YYYY-MM-DD")
|
||||||
);
|
);
|
||||||
|
|
||||||
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
||||||
@@ -53,7 +53,7 @@ export default function DashboardMonthlyEmployeeEfficiency({
|
|||||||
((dailyHrs.productive - dailyHrs.actual) / dailyHrs.actual + 1) * 100;
|
((dailyHrs.productive - dailyHrs.actual) / dailyHrs.actual + 1) * 100;
|
||||||
|
|
||||||
const theValue = {
|
const theValue = {
|
||||||
date: moment(val).format("DD"),
|
date: dayjs(val).format("DD"),
|
||||||
// ...dailyHrs,
|
// ...dailyHrs,
|
||||||
actual: dailyHrs.actual.toFixed(1),
|
actual: dailyHrs.actual.toFixed(1),
|
||||||
productive: dailyHrs.productive.toFixed(1),
|
productive: dailyHrs.productive.toFixed(1),
|
||||||
@@ -159,9 +159,9 @@ export default function DashboardMonthlyEmployeeEfficiency({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DashboardMonthlyEmployeeEfficiencyGql = `
|
export const DashboardMonthlyEmployeeEfficiencyGql = `
|
||||||
monthly_employee_efficiency: timetickets(where: {_and: [{date: {_gte: "${moment()
|
monthly_employee_efficiency: timetickets(where: {_and: [{date: {_gte: "${dayjs()
|
||||||
.startOf("month")
|
.startOf("month")
|
||||||
.format("YYYY-MM-DD")}"}},{date: {_lte: "${moment()
|
.format("YYYY-MM-DD")}"}},{date: {_lte: "${dayjs()
|
||||||
.endOf("month")
|
.endOf("month")
|
||||||
.format("YYYY-MM-DD")}"}} ]}) {
|
.format("YYYY-MM-DD")}"}} ]}) {
|
||||||
actualhrs
|
actualhrs
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
|||||||
async function getCostingData() {
|
async function getCostingData() {
|
||||||
if (data && data.monthly_sales) {
|
if (data && data.monthly_sales) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
console.log('defaults:')
|
||||||
|
console.dir(axios.defaults);
|
||||||
const response = await axios.post("/job/costingmulti", {
|
const response = await axios.post("/job/costingmulti", {
|
||||||
jobids: data.monthly_sales.map((x) => x.id),
|
jobids: data.monthly_sales.map((x) => x.id),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Card } from "antd";
|
import { Card } from "antd";
|
||||||
import moment from "moment";
|
import dayjs from "../../../utils/day";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
@@ -24,7 +24,7 @@ export default function DashboardMonthlyRevenueGraph({ data, ...cardProps }) {
|
|||||||
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
if (!data.monthly_sales) return <DashboardRefreshRequired {...cardProps} />;
|
||||||
|
|
||||||
const jobsByDate = _.groupBy(data.monthly_sales, (item) =>
|
const jobsByDate = _.groupBy(data.monthly_sales, (item) =>
|
||||||
moment(item.date_invoiced).format("YYYY-MM-DD")
|
dayjs(item.date_invoiced).format("YYYY-MM-DD")
|
||||||
);
|
);
|
||||||
|
|
||||||
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
const listOfDays = Utils.ListOfDaysInCurrentMonth();
|
||||||
@@ -43,7 +43,7 @@ export default function DashboardMonthlyRevenueGraph({ data, ...cardProps }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const theValue = {
|
const theValue = {
|
||||||
date: moment(val).format("DD"),
|
date: dayjs(val).format("DD"),
|
||||||
dailySales: dailySales.getAmount() / 100,
|
dailySales: dailySales.getAmount() / 100,
|
||||||
accSales:
|
accSales:
|
||||||
acc.length > 0
|
acc.length > 0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Card, Statistic } from "antd";
|
import { Card, Statistic } from "antd";
|
||||||
import Dinero from "dinero.js";
|
import Dinero from "dinero.js";
|
||||||
import moment from "moment";
|
import dayjs from "../../../utils/day";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import DashboardRefreshRequired from "../refresh-required.component";
|
import DashboardRefreshRequired from "../refresh-required.component";
|
||||||
@@ -36,10 +36,10 @@ export const DashboardProjectedMonthlySalesGql = `
|
|||||||
_or: [
|
_or: [
|
||||||
{_and: [
|
{_and: [
|
||||||
{date_invoiced:{_is_null: false }},
|
{date_invoiced:{_is_null: false }},
|
||||||
{date_invoiced: {_gte: "${moment()
|
{date_invoiced: {_gte: "${dayjs()
|
||||||
.startOf("month")
|
.startOf("month")
|
||||||
.startOf("day")
|
.startOf("day")
|
||||||
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
|
.toISOString()}"}}, {date_invoiced: {_lte: "${dayjs()
|
||||||
.endOf("month")
|
.endOf("month")
|
||||||
.endOf("day")
|
.endOf("day")
|
||||||
.toISOString()}"}}]},
|
.toISOString()}"}}]},
|
||||||
@@ -47,10 +47,10 @@ export const DashboardProjectedMonthlySalesGql = `
|
|||||||
|
|
||||||
_and:[
|
_and:[
|
||||||
{date_invoiced:{_is_null: true }},
|
{date_invoiced:{_is_null: true }},
|
||||||
{actual_completion: {_gte: "${moment()
|
{actual_completion: {_gte: "${dayjs()
|
||||||
.startOf("month")
|
.startOf("month")
|
||||||
.startOf("day")
|
.startOf("day")
|
||||||
.toISOString()}"}}, {actual_completion: {_lte: "${moment()
|
.toISOString()}"}}, {actual_completion: {_lte: "${dayjs()
|
||||||
.endOf("month")
|
.endOf("month")
|
||||||
.endOf("day")
|
.endOf("day")
|
||||||
.toISOString()}"}}
|
.toISOString()}"}}
|
||||||
@@ -61,10 +61,10 @@ _and:[
|
|||||||
{_and: [
|
{_and: [
|
||||||
{date_invoiced: {_is_null: true}},
|
{date_invoiced: {_is_null: true}},
|
||||||
{actual_completion: {_is_null: true}}
|
{actual_completion: {_is_null: true}}
|
||||||
{scheduled_completion: {_gte: "${moment()
|
{scheduled_completion: {_gte: "${dayjs()
|
||||||
.startOf("month")
|
.startOf("month")
|
||||||
.startOf("day")
|
.startOf("day")
|
||||||
.toISOString()}"}}, {scheduled_completion: {_lte: "${moment()
|
.toISOString()}"}}, {scheduled_completion: {_lte: "${dayjs()
|
||||||
.endOf("month")
|
.endOf("month")
|
||||||
.endOf("day")
|
.endOf("day")
|
||||||
.toISOString()}"}}
|
.toISOString()}"}}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Card, Space, Table, Tooltip } from "antd";
|
import { Card, Space, Table, Tooltip } from "antd";
|
||||||
import moment from "moment";
|
import dayjs from "../../../utils/day";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -49,14 +49,14 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
v_vin: item.job.v_vin,
|
v_vin: item.job.v_vin,
|
||||||
vehicleid: item.job.vehicleid,
|
vehicleid: item.job.vehicleid,
|
||||||
note: item.note,
|
note: item.note,
|
||||||
start: moment(item.start).format("hh:mm a"),
|
start: dayjs(item.start).format("hh:mm a"),
|
||||||
title: item.title,
|
title: item.title,
|
||||||
};
|
};
|
||||||
appt.push(i);
|
appt.push(i);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
appt.sort(function (a, b) {
|
appt.sort(function (a, b) {
|
||||||
return new moment(a.start) - new moment(b.start);
|
return new dayjs(a.start) - new dayjs(b.start);
|
||||||
});
|
});
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@@ -189,7 +189,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={t("dashboard.titles.scheduledintoday", {
|
title={t("dashboard.titles.scheduledintoday", {
|
||||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
date: dayjs().startOf("day").format("MM/DD/YYYY"),
|
||||||
})}
|
})}
|
||||||
{...cardProps}
|
{...cardProps}
|
||||||
>
|
>
|
||||||
@@ -209,9 +209,9 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DashboardScheduledInTodayGql = `
|
export const DashboardScheduledInTodayGql = `
|
||||||
scheduled_in_today: appointments(where: {start: {_gte: "${moment()
|
scheduled_in_today: appointments(where: {start: {_gte: "${dayjs()
|
||||||
.startOf("day")
|
.startOf("day")
|
||||||
.toISOString()}", _lte: "${moment()
|
.toISOString()}", _lte: "${dayjs()
|
||||||
.endOf("day")
|
.endOf("day")
|
||||||
.toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) {
|
.toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) {
|
||||||
canceled
|
canceled
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user