Compare commits
170 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0595d58154 | ||
|
|
e0855d370b | ||
|
|
6e8d55cad9 | ||
|
|
4e03756e6e | ||
|
|
edc2a0f122 | ||
|
|
419ae32d32 | ||
|
|
530032b5ea | ||
|
|
3ca48350a6 | ||
|
|
c8979091fd | ||
|
|
7358dee3e0 | ||
|
|
ff151444d2 | ||
|
|
000a2e471e | ||
|
|
94bcfa2afc | ||
|
|
bcb319d571 | ||
|
|
ca246fa6fa | ||
|
|
2dcfa87fa6 | ||
|
|
5f8c878bec | ||
|
|
fb47b45eb6 | ||
|
|
dc1c492c0b | ||
|
|
bdff9f62c0 | ||
|
|
76b25a716c | ||
|
|
86c3806e28 | ||
|
|
548cfefc41 | ||
|
|
ae98566bbd | ||
|
|
29d5465afc | ||
|
|
2b0ecbdd91 | ||
|
|
461bc726aa | ||
|
|
80483b617b | ||
|
|
f9528c5ff7 | ||
|
|
16b0381007 | ||
|
|
da5739be8f | ||
|
|
397ae72626 | ||
|
|
9668a01415 | ||
|
|
6d7f49a858 | ||
|
|
f138ab82fb | ||
|
|
56256be106 | ||
|
|
8dfe98d9d7 | ||
|
|
9e127d1c71 | ||
|
|
cdec9840bf | ||
|
|
9deab7d5d5 | ||
|
|
e46be6c12b | ||
|
|
e1c325c11d | ||
|
|
f02aa36b8d | ||
|
|
d7faf11e27 | ||
|
|
c72d474dc7 | ||
|
|
73058c4405 | ||
|
|
72e8aba546 | ||
|
|
039f1e6a91 | ||
|
|
ddea46ddef | ||
|
|
2ada4ac44b | ||
|
|
6937f33134 | ||
|
|
06a8d4257a | ||
|
|
b478d26c4c | ||
|
|
44a13ca0d5 | ||
|
|
0665bade1b | ||
|
|
51483f62e1 | ||
|
|
48a08819f3 | ||
|
|
8dc41519ce | ||
|
|
e255f0a664 | ||
|
|
7da5d1a4fd | ||
|
|
43621bc6a2 | ||
|
|
909f6e8eb5 | ||
|
|
1e78106224 | ||
|
|
bb872a2b18 | ||
|
|
3fb3773744 | ||
|
|
7a708e32e4 | ||
|
|
d1cd0ea126 | ||
|
|
ee23d83858 | ||
|
|
61d6fdee95 | ||
|
|
ae9bff7e75 | ||
|
|
8031b4f91a | ||
|
|
d7cb8d3753 | ||
|
|
eb3560bd34 | ||
|
|
2fc82aad62 | ||
|
|
4f93b5af13 | ||
|
|
71663d64e4 | ||
|
|
67e475ce21 | ||
|
|
86b84e75cb | ||
|
|
8de4ebd30a | ||
|
|
94dd7c6f69 | ||
|
|
bbbb7867a2 | ||
|
|
1cb4b228ce | ||
|
|
eb96f6467f | ||
|
|
2699b80e1a | ||
|
|
c72b4a25cf | ||
|
|
4a9684ba87 | ||
|
|
1a93e1de41 | ||
|
|
69791a3cdd | ||
|
|
c2d6c980ed | ||
|
|
78a136e277 | ||
|
|
690e65df0b | ||
|
|
a38611a584 | ||
|
|
b6b445dc21 | ||
|
|
0d0f24802f | ||
|
|
cf9b03d073 | ||
|
|
7b61c24461 | ||
|
|
f3db5d83c7 | ||
|
|
63ae37e5a9 | ||
|
|
76bcfce312 | ||
|
|
34c332e077 | ||
|
|
d360bcbb71 | ||
|
|
ba32a71786 | ||
|
|
7d6e61043e | ||
|
|
9fc586434e | ||
|
|
ef0bc8c313 | ||
|
|
aa8a719154 | ||
|
|
5aa3612e52 | ||
|
|
7d16ae5194 | ||
|
|
663dfe0441 | ||
|
|
49131ba68b | ||
|
|
c2dbdbd6cf | ||
|
|
0635c651a2 | ||
|
|
05667dd322 | ||
|
|
d3654ec16e | ||
|
|
ab299619dd | ||
|
|
e9d1f3af67 | ||
|
|
46b58a6e1b | ||
|
|
3dcc1fe7e0 | ||
|
|
c6012f7335 | ||
|
|
0c7b5087f1 | ||
|
|
7589f78fe1 | ||
|
|
2e589c44a6 | ||
|
|
0074c73c2a | ||
|
|
213a02d5f2 | ||
|
|
6e377f98a7 | ||
|
|
3a3b3af13f | ||
|
|
1f14688199 | ||
|
|
bc50f5e983 | ||
|
|
33a1ac9be4 | ||
|
|
2c8d1accea | ||
|
|
351d6f274b | ||
|
|
e43bfe0d3a | ||
|
|
61ed0087e7 | ||
|
|
17791ac971 | ||
|
|
a0cb30f986 | ||
|
|
07b46ed92b | ||
|
|
79dce5d069 | ||
|
|
e5d8cc2bea | ||
|
|
7b83430c02 | ||
|
|
ed0da08326 | ||
|
|
c8460f6092 | ||
|
|
d2e4b7d9ec | ||
|
|
1c5f74e4f0 | ||
|
|
46d514ad1c | ||
|
|
0514fbe89d | ||
|
|
87391ff06a | ||
|
|
b2c8e45d5e | ||
|
|
1261e8001b | ||
|
|
83e4fb3dc4 | ||
|
|
a67fb3576c | ||
|
|
65157a094f | ||
|
|
25173b0903 | ||
|
|
64f56d20dd | ||
|
|
5b3c547316 | ||
|
|
16d040daf9 | ||
|
|
a22c4bdf8c | ||
|
|
9cb2a4a021 | ||
|
|
95c9978ee7 | ||
|
|
fe80256a40 | ||
|
|
b0d1a7b65e | ||
|
|
ad79344709 | ||
|
|
5c164f807d | ||
|
|
a043f7be24 | ||
|
|
8d9611333c | ||
|
|
02bb2c06eb | ||
|
|
008bcaf41b | ||
|
|
a9dbfbd231 | ||
|
|
1df870ee4e | ||
|
|
517a087186 | ||
|
|
51747c554e |
@@ -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:
|
||||||
@@ -58,7 +56,26 @@ jobs:
|
|||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-production/"
|
to: "s3://imex-online-production/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify
|
|
||||||
|
app-beta-build:
|
||||||
|
docker:
|
||||||
|
- image: cimg/node:18.18.2
|
||||||
|
resource_class: xlarge
|
||||||
|
working_directory: ~/repo/client
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- checkout:
|
||||||
|
path: ~/repo
|
||||||
|
- run:
|
||||||
|
name: Install Dependencies
|
||||||
|
command: npm i
|
||||||
|
|
||||||
|
- run: npm run build
|
||||||
|
|
||||||
|
- aws-s3/sync:
|
||||||
|
from: build
|
||||||
|
to: "s3://imex-online-beta/"
|
||||||
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
test-hasura-migrate:
|
test-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
@@ -99,7 +116,27 @@ jobs:
|
|||||||
from: build
|
from: build
|
||||||
to: "s3://imex-online-test/"
|
to: "s3://imex-online-test/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
- jira/notify
|
|
||||||
|
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:
|
||||||
@@ -142,6 +179,10 @@ 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:
|
||||||
@@ -151,6 +192,10 @@ workflows:
|
|||||||
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:
|
||||||
|
|||||||
@@ -1,13 +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":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
VITE_APP_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='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
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
|
||||||
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: "imexonline",
|
// 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": "#a51d1d" }
|
},
|
||||||
: {
|
},
|
||||||
//"@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,12 +2,12 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/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="ImEX 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="logo192.png" />
|
<link rel="apple-touch-icon" href="public/logo192.png" />
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.$crisp = [];
|
window.$crisp = [];
|
||||||
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
|
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
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.
|
||||||
@@ -82,5 +82,7 @@
|
|||||||
<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
|
||||||
|
|||||||
24511
client/package-lock.json
generated
24511
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,102 +1,111 @@
|
|||||||
{
|
{
|
||||||
"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",
|
||||||
|
"@reduxjs/toolkit": "^2.1.0",
|
||||||
"@sentry/cli": "^2.27.0",
|
"@sentry/cli": "^2.27.0",
|
||||||
"@sentry/react": "^7.99.0",
|
"@sentry/react": "^7.99.0",
|
||||||
"@sentry/tracing": "^7.40.0",
|
"@sentry/tracing": "^7.99.0",
|
||||||
"@splitsoftware/splitio-react": "^1.8.1",
|
"@splitsoftware/splitio-react": "^1.11.0",
|
||||||
"@tanem/react-nprogress": "^5.0.8",
|
"@tanem/react-nprogress": "^5.0.51",
|
||||||
"antd": "^4.24.8",
|
"@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",
|
||||||
"apollo-link-sentry": "^3.3.0",
|
"apollo-link-sentry": "^3.3.0",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.6.7",
|
||||||
"craco-less": "^2.0.0",
|
"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 && npm run sentry:sourcemaps",
|
"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"
|
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
@@ -122,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
|
||||||
|
|||||||
@@ -1,47 +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";
|
import * as Sentry from "@sentry/react";
|
||||||
|
|
||||||
moment.locale("en-US");
|
import themeProvider from "./themeProvider";
|
||||||
|
|
||||||
|
dayjs.locale("en");
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
core: {
|
||||||
|
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
||||||
|
key: "anon",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export const factory = SplitSdk(config);
|
||||||
|
|
||||||
export const factory = SplitSdk({
|
|
||||||
core: {
|
|
||||||
authorizationKey: process.env.REACT_APP_SPLIT_API,
|
|
||||||
key: "anon",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function AppContainer() {
|
function AppContainer() {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ApolloProvider client={client}>
|
<ApolloProvider client={client}>
|
||||||
<ConfigProvider
|
<ConfigProvider
|
||||||
//componentSize="small"
|
//componentSize="small"
|
||||||
input={{ autoComplete: "new-password" }}
|
input={{autoComplete: "new-password"}}
|
||||||
locale={enLocale}
|
locale={enLocale}
|
||||||
form={{
|
theme={themeProvider}
|
||||||
validateMessages: {
|
form={{
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
validateMessages: {
|
||||||
required: t("general.validation.required", { label: "${label}" }),
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
},
|
required: t("general.validation.required", {label: "${label}"}),
|
||||||
}}
|
},
|
||||||
>
|
}}
|
||||||
<GlobalLoadingBar />
|
>
|
||||||
<SplitFactory factory={factory}>
|
<GlobalLoadingBar/>
|
||||||
<App />
|
<SplitFactoryProvider factory={factory}>
|
||||||
</SplitFactory>
|
<App/>
|
||||||
</ConfigProvider>
|
</SplitFactoryProvider>
|
||||||
</ApolloProvider>
|
</ConfigProvider>
|
||||||
);
|
</ApolloProvider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Sentry.withProfiler(AppContainer);
|
export default Sentry.withProfiler(AppContainer);
|
||||||
|
|||||||
@@ -1,164 +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/handleBeta";
|
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();
|
|
||||||
}, [checkUserSession, setOnline]);
|
|
||||||
|
|
||||||
//const b = Grid.useBreakpoint();
|
useEffect(() => {
|
||||||
// console.log("Breakpoints:", b);
|
if (!navigator.onLine) {
|
||||||
|
setOnline(false);
|
||||||
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);
|
|
||||||
|
|
||||||
if (client.getTreatment("LogRocket_Tracking") === "on") {
|
|
||||||
console.log("LR Start");
|
|
||||||
LogRocket.init("gvfvfw/bodyshopapp");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [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>
|
|
||||||
}
|
}
|
||||||
/>
|
|
||||||
|
checkUserSession();
|
||||||
|
}, [checkUserSession, setOnline]);
|
||||||
|
|
||||||
|
//const b = Grid.useBreakpoint();
|
||||||
|
// console.log("Breakpoints:", b);
|
||||||
|
|
||||||
|
// Associate event listeners, memoize to prevent multiple listeners being added
|
||||||
|
useEffect(() => {
|
||||||
|
const offlineListener = (e) => {
|
||||||
|
setOnline(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onlineListener = (e) => {
|
||||||
|
setOnline(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!listenersAdded) {
|
||||||
|
console.log('Added events for offline and online');
|
||||||
|
window.addEventListener("offline", offlineListener);
|
||||||
|
window.addEventListener("online", onlineListener);
|
||||||
|
setListenersAdded(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("offline", offlineListener);
|
||||||
|
window.removeEventListener("online", onlineListener);
|
||||||
|
}
|
||||||
|
}, [setOnline, listenersAdded]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentUser.authorized && bodyshop) {
|
||||||
|
client.setAttribute("imexshopid", bodyshop.imexshopid);
|
||||||
|
|
||||||
|
if (
|
||||||
|
client.getTreatment("LogRocket_Tracking") === "on" ||
|
||||||
|
window.location.hostname === 'beta.imex.online'
|
||||||
|
) {
|
||||||
|
console.log("LR Start");
|
||||||
|
LogRocket.init("gvfvfw/bodyshopapp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [bodyshop, client, currentUser.authorized]);
|
||||||
|
|
||||||
|
if (currentUser.authorized === null) {
|
||||||
|
return <LoadingSpinner message={t("general.labels.loggingin")}/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
);
|
);
|
||||||
|
|
||||||
handleBeta();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Switch>
|
|
||||||
<Suspense fallback={<LoadingSpinner message="ImEX Online" />}>
|
|
||||||
<ErrorBoundary>
|
|
||||||
<Route exact path="/" component={LandingPage} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
<ErrorBoundary>
|
|
||||||
<Route exact path="/signin" component={SignInPage} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
<ErrorBoundary>
|
|
||||||
<Route exact path="/resetpassword" component={ResetPassword} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
<ErrorBoundary>
|
|
||||||
<Route exact path="/csi/:surveyId" component={CsiPage} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
<ErrorBoundary>
|
|
||||||
<Route exact path="/disclaimer" component={DisclaimerPage} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
<ErrorBoundary>
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path="/mp/:paymentIs"
|
|
||||||
component={MobilePaymentContainer}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
<ErrorBoundary>
|
|
||||||
<PrivateRoute
|
|
||||||
isAuthorized={currentUser.authorized}
|
|
||||||
path="/manage"
|
|
||||||
component={ManagePage}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
<ErrorBoundary>
|
|
||||||
<PrivateRoute
|
|
||||||
isAuthorized={currentUser.authorized}
|
|
||||||
path="/tech"
|
|
||||||
component={TechPageContainer}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
<ErrorBoundary>
|
|
||||||
<PrivateRoute
|
|
||||||
isAuthorized={currentUser.authorized}
|
|
||||||
path="/edit"
|
|
||||||
component={DocumentEditorContainer}
|
|
||||||
/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
</Suspense>
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
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;
|
||||||
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";
|
||||||
@@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import {
|
import {
|
||||||
DELETE_BILL_LINE,
|
DELETE_BILL_LINE,
|
||||||
INSERT_NEW_BILL_LINES,
|
INSERT_NEW_BILL_LINES,
|
||||||
UPDATE_BILL_LINE,
|
UPDATE_BILL_LINE
|
||||||
} from "../../graphql/bill-lines.queries";
|
} from "../../graphql/bill-lines.queries";
|
||||||
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
@@ -26,143 +26,142 @@ import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-galler
|
|||||||
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 (
|
||||||
<>
|
<>
|
||||||
@@ -179,9 +178,9 @@ export function BillDetailEditcontainer({
|
|||||||
<BillDetailEditReturn data={data} />
|
<BillDetailEditReturn data={data} />
|
||||||
<BillPrintButton billid={search.billid} />
|
<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")}
|
||||||
>
|
>
|
||||||
@@ -208,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>
|
||||||
|
|||||||
@@ -346,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()}
|
||||||
|
|||||||
@@ -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,54 +20,38 @@ 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) => {
|
const handleFederalTaxExemptSwitchToggle = (checked) => {
|
||||||
// Early gate
|
// Early gate
|
||||||
@@ -97,256 +71,256 @@ export function BillFormComponent({
|
|||||||
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();
|
||||||
},
|
},
|
||||||
@@ -401,15 +375,15 @@ export function BillFormComponent({
|
|||||||
>
|
>
|
||||||
<CurrencyInput min={0} />
|
<CurrencyInput min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
|
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
span={2}
|
span={2}
|
||||||
label={t("bills.labels.federal_tax_exempt")}
|
label={t("bills.labels.federal_tax_exempt")}
|
||||||
name="federal_tax_exempt"
|
name="federal_tax_exempt"
|
||||||
>
|
>
|
||||||
<Switch onChange={handleFederalTaxExemptSwitchToggle} />
|
<Switch onChange={handleFederalTaxExemptSwitchToggle} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
) : null}
|
) : null}
|
||||||
<Form.Item shouldUpdate span={13}>
|
<Form.Item shouldUpdate span={13}>
|
||||||
{() => {
|
{() => {
|
||||||
const values = form.getFieldsValue([
|
const values = form.getFieldsValue([
|
||||||
@@ -486,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,5 +1,5 @@
|
|||||||
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,
|
||||||
Form,
|
Form,
|
||||||
@@ -41,11 +41,14 @@ 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"],
|
const { treatments: {Simple_Inventory} } = useSplitTreatments({
|
||||||
{},
|
attributes: {},
|
||||||
bodyshop && bodyshop.imexshopid
|
names: ["Simple_Inventory"],
|
||||||
);
|
splitKey: bodyshop && bodyshop.imexshopid,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const columns = (remove) => {
|
const columns = (remove) => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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,6 +58,9 @@ 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}${
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -16,42 +16,28 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
export function ChatPrintButton({ conversation }) {
|
export function ChatPrintButton({ conversation }) {
|
||||||
const [loading, setLoading] = useState(false);
|
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 (
|
return (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<PrinterOutlined
|
<PrinterOutlined onClick={() => generateDocument('p')}/>
|
||||||
onClick={() => {
|
<MailOutlined onClick={() => generateDocument('e')}/>
|
||||||
setLoading(true);
|
|
||||||
GenerateDocument(
|
|
||||||
{
|
|
||||||
name: TemplateList("messaging").conversation_list.key,
|
|
||||||
variables: { id: conversation.id },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
subject: TemplateList("messaging").conversation_list.subject,
|
|
||||||
},
|
|
||||||
"p",
|
|
||||||
conversation.id
|
|
||||||
);
|
|
||||||
setLoading(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<MailOutlined
|
|
||||||
onClick={() => {
|
|
||||||
setLoading(true);
|
|
||||||
GenerateDocument(
|
|
||||||
{
|
|
||||||
name: TemplateList("messaging").conversation_list.key,
|
|
||||||
variables: { id: conversation.id },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
subject: TemplateList("messaging").conversation_list.subject,
|
|
||||||
},
|
|
||||||
"e",
|
|
||||||
conversation.id
|
|
||||||
);
|
|
||||||
setLoading(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{loading && <Spin />}
|
{loading && <Spin />}
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,7 +1,8 @@
|
|||||||
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";
|
||||||
@@ -263,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 (
|
||||||
@@ -304,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 (
|
||||||
@@ -340,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 (
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -77,8 +76,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
const mileageOver = nextservicekm ? nextservicekm <= mileage : false;
|
const mileageOver = nextservicekm ? nextservicekm <= mileage : false;
|
||||||
|
|
||||||
const dueForService =
|
const dueForService =
|
||||||
nextservicedate &&
|
nextservicedate && dayjs(nextservicedate).endOf('day').isSameOrBefore(dayjs());
|
||||||
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Space>
|
||||||
@@ -229,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")}
|
||||||
@@ -237,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
|
||||||
|
|||||||
@@ -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";
|
||||||
@@ -22,12 +22,13 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
|||||||
if (!data.scheduled_out_today)
|
if (!data.scheduled_out_today)
|
||||||
return <DashboardRefreshRequired {...cardProps} />;
|
return <DashboardRefreshRequired {...cardProps} />;
|
||||||
|
|
||||||
data.scheduled_out_today.forEach((item) => {
|
const filteredScheduledOutToday = data.scheduled_out_today.map((item) => {
|
||||||
item.scheduled_completion= moment(item.scheduled_completion).format("hh:mm a")
|
return {
|
||||||
});
|
...item,
|
||||||
data.scheduled_out_today.sort(function (a, b) {
|
scheduled_completion: dayjs(item.scheduled_completion).format("hh:mm a"),
|
||||||
return new Date(a.scheduled_completion) - new Date(b.scheduled_completion);
|
timestamp: dayjs(item.scheduled_completion).valueOf(),
|
||||||
});
|
}
|
||||||
|
}).sort((a, b) => a.timestamp - b.timestamp);
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -159,7 +160,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={t("dashboard.titles.scheduledouttoday", {
|
title={t("dashboard.titles.scheduledouttoday", {
|
||||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
date: dayjs().startOf("day").format("MM/DD/YYYY"),
|
||||||
})}
|
})}
|
||||||
{...cardProps}
|
{...cardProps}
|
||||||
>
|
>
|
||||||
@@ -171,7 +172,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
|||||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
style={{ height: "85%" }}
|
style={{ height: "85%" }}
|
||||||
dataSource={data.scheduled_out_today}
|
dataSource={filteredScheduledOutToday}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -183,8 +184,8 @@ export const DashboardScheduledOutTodayGql = `
|
|||||||
date_invoiced: {_is_null: true},
|
date_invoiced: {_is_null: true},
|
||||||
ro_number: {_is_null: false},
|
ro_number: {_is_null: false},
|
||||||
voided: {_eq: false},
|
voided: {_eq: false},
|
||||||
scheduled_completion: {_gte: "${moment().startOf("day").toISOString()}",
|
scheduled_completion: {_gte: "${dayjs().startOf("day").toISOString()}",
|
||||||
_lte: "${moment().endOf("day").toISOString()}"}}) {
|
_lte: "${dayjs().endOf("day").toISOString()}"}}) {
|
||||||
alt_transport
|
alt_transport
|
||||||
clm_no
|
clm_no
|
||||||
jobid: id
|
jobid: id
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import Icon, { SyncOutlined } from "@ant-design/icons";
|
import Icon, { SyncOutlined } from "@ant-design/icons";
|
||||||
import { gql, useMutation, useQuery } from "@apollo/client";
|
import { gql, useMutation, useQuery } from "@apollo/client";
|
||||||
import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd";
|
import { Button, Dropdown, Space, notification } from "antd";
|
||||||
|
import {PageHeader} from "@ant-design/pro-layout";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Responsive, WidthProvider } from "react-grid-layout";
|
import { Responsive, WidthProvider } from "react-grid-layout";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -122,19 +123,15 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
|
|||||||
[data]
|
[data]
|
||||||
);
|
);
|
||||||
const existingLayoutKeys = state.items.map((i) => i.i);
|
const existingLayoutKeys = state.items.map((i) => i.i);
|
||||||
const addComponentOverlay = (
|
|
||||||
<Menu onClick={handleAddComponent}>
|
const menuItems = Object.keys(componentList).map((key) => ({
|
||||||
{Object.keys(componentList).map((key) => (
|
key: key,
|
||||||
<Menu.Item
|
label: componentList[key].label,
|
||||||
key={key}
|
value: key,
|
||||||
value={key}
|
disabled: existingLayoutKeys.includes(key),
|
||||||
disabled={existingLayoutKeys.includes(key)}
|
}));
|
||||||
>
|
|
||||||
{componentList[key].label}
|
const menu = {items: menuItems, onClick: handleAddComponent};
|
||||||
</Menu.Item>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
|
||||||
@@ -146,7 +143,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
|
|||||||
<Button onClick={() => refetch()}>
|
<Button onClick={() => refetch()}>
|
||||||
<SyncOutlined />
|
<SyncOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
<Dropdown overlay={addComponentOverlay} trigger={["click"]}>
|
<Dropdown menu={menu} trigger={["click"]}>
|
||||||
<Button>{t("dashboard.actions.addcomponent")}</Button>
|
<Button>{t("dashboard.actions.addcomponent")}</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -276,7 +273,7 @@ const componentList = {
|
|||||||
},
|
},
|
||||||
ScheduleInToday: {
|
ScheduleInToday: {
|
||||||
label: i18next.t("dashboard.titles.scheduledintoday", {
|
label: i18next.t("dashboard.titles.scheduledintoday", {
|
||||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
date: dayjs().startOf("day").format("MM/DD/YYYY"),
|
||||||
}),
|
}),
|
||||||
component: DashboardScheduledInToday,
|
component: DashboardScheduledInToday,
|
||||||
gqlFragment: DashboardScheduledInTodayGql,
|
gqlFragment: DashboardScheduledInTodayGql,
|
||||||
@@ -287,7 +284,7 @@ const componentList = {
|
|||||||
},
|
},
|
||||||
ScheduleOutToday: {
|
ScheduleOutToday: {
|
||||||
label: i18next.t("dashboard.titles.scheduledouttoday", {
|
label: i18next.t("dashboard.titles.scheduledouttoday", {
|
||||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
date: dayjs().startOf("day").format("MM/DD/YYYY"),
|
||||||
}),
|
}),
|
||||||
component: DashboardScheduledOutToday,
|
component: DashboardScheduledOutToday,
|
||||||
gqlFragment: DashboardScheduledOutTodayGql,
|
gqlFragment: DashboardScheduledOutTodayGql,
|
||||||
@@ -310,10 +307,10 @@ const createDashboardQuery = (state) => {
|
|||||||
${componentBasedAdditions || ""}
|
${componentBasedAdditions || ""}
|
||||||
monthly_sales: jobs(where: {_and: [
|
monthly_sales: jobs(where: {_and: [
|
||||||
{ voided: {_eq: false}},
|
{ voided: {_eq: false}},
|
||||||
{date_invoiced: {_gte: "${moment()
|
{date_invoiced: {_gte: "${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()}"}}]}) {
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ export default function DataLabel({
|
|||||||
hideIfNull,
|
hideIfNull,
|
||||||
children,
|
children,
|
||||||
vertical,
|
vertical,
|
||||||
visible = true,
|
open = true,
|
||||||
valueStyle = {},
|
valueStyle = {},
|
||||||
valueClassName,
|
valueClassName,
|
||||||
onValueClick,
|
onValueClick,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
if (!visible || (hideIfNull && !!!children)) return null;
|
if (!open || (hideIfNull && !!!children)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...props} style={{ display: "flex" }}>
|
<div {...props} style={{ display: "flex" }}>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkVehicles);
|
export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkVehicles);
|
||||||
|
|
||||||
export function DmsCdkVehicles({ bodyshop, form, socket, job }) {
|
export function DmsCdkVehicles({ bodyshop, form, socket, job }) {
|
||||||
const [visible, setVisible] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [selectedModel, setSelectedModel] = useState(null);
|
const [selectedModel, setSelectedModel] = useState(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -51,14 +51,14 @@ export function DmsCdkVehicles({ bodyshop, form, socket, job }) {
|
|||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
width={"90%"}
|
width={"90%"}
|
||||||
visible={visible}
|
open={open}
|
||||||
onCancel={() => setVisible(false)}
|
onCancel={() => setOpen(false)}
|
||||||
onOk={() => {
|
onOk={() => {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
dms_make: selectedModel.makecode,
|
dms_make: selectedModel.makecode,
|
||||||
dms_model: selectedModel.modelcode,
|
dms_model: selectedModel.modelcode,
|
||||||
});
|
});
|
||||||
setVisible(false);
|
setOpen(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{error && <AlertComponent error={error.message} />}
|
{error && <AlertComponent error={error.message} />}
|
||||||
@@ -90,7 +90,7 @@ export function DmsCdkVehicles({ bodyshop, form, socket, job }) {
|
|||||||
</Modal>
|
</Modal>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setVisible(true);
|
setOpen(true);
|
||||||
callSearch({
|
callSearch({
|
||||||
variables: {
|
variables: {
|
||||||
search: job && job.v_model_desc && job.v_model_desc.substr(0, 3),
|
search: job && job.v_model_desc && job.v_model_desc.substr(0, 3),
|
||||||
|
|||||||
@@ -21,29 +21,29 @@ export default connect(
|
|||||||
export function DmsCustomerSelector({ bodyshop }) {
|
export function DmsCustomerSelector({ bodyshop }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [customerList, setcustomerList] = useState([]);
|
const [customerList, setcustomerList] = useState([]);
|
||||||
const [visible, setVisible] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
||||||
const [dmsType, setDmsType] = useState("cdk");
|
const [dmsType, setDmsType] = useState("cdk");
|
||||||
|
|
||||||
socket.on("cdk-select-customer", (customerList, callback) => {
|
socket.on("cdk-select-customer", (customerList, callback) => {
|
||||||
setVisible(true);
|
setOpen(true);
|
||||||
setDmsType("cdk");
|
setDmsType("cdk");
|
||||||
setcustomerList(customerList);
|
setcustomerList(customerList);
|
||||||
});
|
});
|
||||||
socket.on("pbs-select-customer", (customerList, callback) => {
|
socket.on("pbs-select-customer", (customerList, callback) => {
|
||||||
setVisible(true);
|
setOpen(true);
|
||||||
setDmsType("pbs");
|
setDmsType("pbs");
|
||||||
setcustomerList(customerList);
|
setcustomerList(customerList);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onUseSelected = () => {
|
const onUseSelected = () => {
|
||||||
setVisible(false);
|
setOpen(false);
|
||||||
socket.emit(`${dmsType}-selected-customer`, selectedCustomer);
|
socket.emit(`${dmsType}-selected-customer`, selectedCustomer);
|
||||||
setSelectedCustomer(null);
|
setSelectedCustomer(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUseGeneric = () => {
|
const onUseGeneric = () => {
|
||||||
setVisible(false);
|
setOpen(false);
|
||||||
socket.emit(
|
socket.emit(
|
||||||
`${dmsType}-selected-customer`,
|
`${dmsType}-selected-customer`,
|
||||||
bodyshop.cdk_configuration.generic_customer_number
|
bodyshop.cdk_configuration.generic_customer_number
|
||||||
@@ -52,7 +52,7 @@ export function DmsCustomerSelector({ bodyshop }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onCreateNew = () => {
|
const onCreateNew = () => {
|
||||||
setVisible(false);
|
setOpen(false);
|
||||||
socket.emit(`${dmsType}-selected-customer`, null);
|
socket.emit(`${dmsType}-selected-customer`, null);
|
||||||
setSelectedCustomer(null);
|
setSelectedCustomer(null);
|
||||||
};
|
};
|
||||||
@@ -114,7 +114,7 @@ export function DmsCustomerSelector({ bodyshop }) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!visible) return null;
|
if (!open) return null;
|
||||||
return (
|
return (
|
||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
<Table
|
<Table
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Divider, Space, Tag, Timeline } from "antd";
|
import { Divider, Space, Tag, Timeline } from "antd";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
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";
|
||||||
@@ -22,20 +22,22 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
|
|||||||
|
|
||||||
export function DmsLogEvents({ socket, logs, bodyshop }) {
|
export function DmsLogEvents({ socket, logs, bodyshop }) {
|
||||||
return (
|
return (
|
||||||
<Timeline pending
|
<Timeline
|
||||||
reverse={true}
|
pending
|
||||||
>
|
reverse={true}
|
||||||
{logs.map((log, idx) => (
|
items={logs.map((log, idx) => ({
|
||||||
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}>
|
key: idx,
|
||||||
|
color: LogLevelHierarchy(log.level),
|
||||||
|
children: (
|
||||||
<Space wrap align="start" style={{}}>
|
<Space wrap align="start" style={{}}>
|
||||||
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
|
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
|
||||||
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:mm:ss")}</span>
|
<span>{dayjs(log.timestamp).format("MM/DD/YYYY HH:mm:ss")}</span>
|
||||||
<Divider type="vertical" />
|
<Divider type="vertical" />
|
||||||
<span>{log.message}</span>
|
<span>{log.message}</span>
|
||||||
</Space>
|
</Space>
|
||||||
</Timeline.Item>
|
),
|
||||||
))}
|
}))}
|
||||||
</Timeline>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,26 @@
|
|||||||
import { DeleteFilled, DownOutlined } from "@ant-design/icons";
|
import {DeleteFilled, DownOutlined} from "@ant-design/icons";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Divider,
|
Divider,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Form,
|
Form,
|
||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
Menu,
|
Select,
|
||||||
Select,
|
Space,
|
||||||
Space,
|
Statistic,
|
||||||
Statistic,
|
Switch,
|
||||||
Switch,
|
Typography,
|
||||||
Typography,
|
|
||||||
} from "antd";
|
} 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 { connect } from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import { determineDmsType } from "../../pages/dms/dms.container";
|
import {determineDmsType} from "../../pages/dms/dms.container";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||||
import i18n from "../../translations/i18n";
|
import i18n from "../../translations/i18n";
|
||||||
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
||||||
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
||||||
@@ -30,440 +29,425 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component"
|
|||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
|
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
|
||||||
|
|
||||||
export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
export function DmsPostForm({bodyshop, socket, job, logsRef}) {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const handlePayerSelect = (value, index) => {
|
const handlePayerSelect = (value, index) => {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
payers: form.getFieldValue("payers").map((payer, mapIndex) => {
|
payers: form.getFieldValue("payers").map((payer, mapIndex) => {
|
||||||
if (index !== mapIndex) return payer;
|
if (index !== mapIndex) return payer;
|
||||||
const cdkPayer =
|
const cdkPayer =
|
||||||
bodyshop.cdk_configuration.payers &&
|
bodyshop.cdk_configuration.payers &&
|
||||||
bodyshop.cdk_configuration.payers.find((i) => i.name === value);
|
bodyshop.cdk_configuration.payers.find((i) => i.name === value);
|
||||||
|
|
||||||
if (!cdkPayer) return payer;
|
if (!cdkPayer) return payer;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...cdkPayer,
|
...cdkPayer,
|
||||||
dms_acctnumber: cdkPayer.dms_acctnumber,
|
dms_acctnumber: cdkPayer.dms_acctnumber,
|
||||||
controlnumber: job && job[cdkPayer.control_type],
|
controlnumber: job && job[cdkPayer.control_type],
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleFinish = (values) => {
|
|
||||||
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
|
|
||||||
jobid: job.id,
|
|
||||||
txEnvelope: values,
|
|
||||||
});
|
|
||||||
console.log(logsRef);
|
|
||||||
if (logsRef) {
|
|
||||||
console.log("executing", logsRef);
|
|
||||||
logsRef.curent &&
|
|
||||||
logsRef.current.scrollIntoView({
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
const handleFinish = (values) => {
|
||||||
<Card title={t("jobs.labels.dms.postingform")}>
|
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
|
||||||
<Form
|
jobid: job.id,
|
||||||
form={form}
|
txEnvelope: values,
|
||||||
layout="vertical"
|
});
|
||||||
onFinish={handleFinish}
|
console.log(logsRef);
|
||||||
initialValues={{
|
if (logsRef) {
|
||||||
story: `${t("jobs.labels.dms.defaultstory", {
|
console.log("executing", logsRef);
|
||||||
ro_number: job.ro_number,
|
logsRef.curent &&
|
||||||
ownr_nm: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
|
logsRef.current.scrollIntoView({
|
||||||
job.ownr_co_nm || ""
|
behavior: "smooth",
|
||||||
}`.trim(),
|
});
|
||||||
ins_co_nm: job.ins_co_nm || "N/A",
|
}
|
||||||
clm_po: `${job.clm_no ? `${job.clm_no} ` : ""}${
|
};
|
||||||
job.po_number || ""
|
|
||||||
}`,
|
|
||||||
}).trim()}.${
|
|
||||||
job.area_of_damage && job.area_of_damage.impact1
|
|
||||||
? " " +
|
|
||||||
t("jobs.labels.dms.damageto", {
|
|
||||||
area_of_damage:
|
|
||||||
(job.area_of_damage && job.area_of_damage.impact1) ||
|
|
||||||
"UNKNOWN",
|
|
||||||
})
|
|
||||||
: ""
|
|
||||||
}`.substr(0, 239),
|
|
||||||
inservicedate: moment("2019-01-01"),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<LayoutFormRow grow>
|
|
||||||
<Form.Item
|
|
||||||
name="journal"
|
|
||||||
label={t("jobs.fields.dms.journal")}
|
|
||||||
initialValue={
|
|
||||||
bodyshop.cdk_configuration &&
|
|
||||||
bodyshop.cdk_configuration.default_journal
|
|
||||||
}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="kmin"
|
|
||||||
label={t("jobs.fields.kmin")}
|
|
||||||
initialValue={job && job.kmin}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber disabled />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="kmout"
|
|
||||||
label={t("jobs.fields.kmout")}
|
|
||||||
initialValue={job && job.kmout}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber disabled />
|
|
||||||
</Form.Item>
|
|
||||||
</LayoutFormRow>
|
|
||||||
|
|
||||||
{bodyshop.cdk_dealerid && (
|
return (
|
||||||
<div>
|
<Card title={t("jobs.labels.dms.postingform")}>
|
||||||
<LayoutFormRow style={{ justifyContent: "center" }} grow>
|
<Form
|
||||||
<Form.Item
|
form={form}
|
||||||
name="dms_make"
|
layout="vertical"
|
||||||
label={t("jobs.fields.dms.dms_make")}
|
onFinish={handleFinish}
|
||||||
rules={[
|
initialValues={{
|
||||||
{
|
story: `${t("jobs.labels.dms.defaultstory", {
|
||||||
required: true,
|
ro_number: job.ro_number,
|
||||||
},
|
ownr_nm: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
|
||||||
]}
|
job.ownr_co_nm || ""
|
||||||
>
|
}`.trim(),
|
||||||
<Input disabled />
|
ins_co_nm: job.ins_co_nm || "N/A",
|
||||||
</Form.Item>
|
clm_po: `${job.clm_no ? `${job.clm_no} ` : ""}${
|
||||||
<Form.Item
|
job.po_number || ""
|
||||||
name="dms_model"
|
}`,
|
||||||
label={t("jobs.fields.dms.dms_model")}
|
}).trim()}.${
|
||||||
rules={[
|
job.area_of_damage && job.area_of_damage.impact1
|
||||||
{
|
? " " +
|
||||||
required: true,
|
t("jobs.labels.dms.damageto", {
|
||||||
},
|
area_of_damage:
|
||||||
]}
|
(job.area_of_damage && job.area_of_damage.impact1) ||
|
||||||
>
|
"UNKNOWN",
|
||||||
<Input disabled />
|
})
|
||||||
</Form.Item>
|
: ""
|
||||||
<Form.Item
|
}`.slice(0, 239),
|
||||||
name="inservicedate"
|
inservicedate: dayjs("2019-01-01"),
|
||||||
label={t("jobs.fields.dms.inservicedate")}
|
}}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<LayoutFormRow grow>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
</LayoutFormRow>
|
name="journal"
|
||||||
<Space>
|
label={t("jobs.fields.dms.journal")}
|
||||||
<DmsCdkMakes form={form} socket={socket} job={job} />
|
initialValue={
|
||||||
<DmsCdkMakesRefetch />
|
bodyshop.cdk_configuration &&
|
||||||
<Form.Item
|
bodyshop.cdk_configuration.default_journal
|
||||||
name="dms_unsold"
|
|
||||||
label={t("jobs.fields.dms.dms_unsold")}
|
|
||||||
initialValue={false}
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
name="dms_model_override"
|
|
||||||
label={t("jobs.fields.dms.dms_model_override")}
|
|
||||||
initialValue={false}
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Form.Item
|
|
||||||
name="story"
|
|
||||||
label={t("jobs.fields.dms.story")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input.TextArea maxLength={240} />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
<Space size="large" wrap align="center">
|
|
||||||
<Statistic
|
|
||||||
title={t("jobs.fields.ded_amt")}
|
|
||||||
value={Dinero(
|
|
||||||
job.job_totals.totals.custPayable.deductible
|
|
||||||
).toFormat()}
|
|
||||||
/>
|
|
||||||
<Statistic
|
|
||||||
title={t("jobs.labels.total_cust_payable")}
|
|
||||||
value={Dinero(job.job_totals.totals.custPayable.total).toFormat()}
|
|
||||||
/>
|
|
||||||
<Statistic
|
|
||||||
title={t("jobs.labels.net_repairs")}
|
|
||||||
value={Dinero(job.job_totals.totals.net_repairs).toFormat()}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
<Form.List name={["payers"]}>
|
|
||||||
{(fields, { add, remove }) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<Form.Item key={field.key}>
|
|
||||||
<Space wrap>
|
|
||||||
<Form.Item
|
|
||||||
label={t("jobs.fields.dms.payer.name")}
|
|
||||||
key={`${index}name`}
|
|
||||||
name={[field.name, "name"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
style={{ minWidth: "15rem" }}
|
|
||||||
onSelect={(value) => handlePayerSelect(value, index)}
|
|
||||||
>
|
|
||||||
{bodyshop.cdk_configuration &&
|
|
||||||
bodyshop.cdk_configuration.payers &&
|
|
||||||
bodyshop.cdk_configuration.payers.map((payer) => (
|
|
||||||
<Select.Option key={payer.name}>
|
|
||||||
{payer.name}
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label={t("jobs.fields.dms.payer.dms_acctnumber")}
|
|
||||||
key={`${index}dms_acctnumber`}
|
|
||||||
name={[field.name, "dms_acctnumber"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input disabled />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label={t("jobs.fields.dms.payer.amount")}
|
|
||||||
key={`${index}amount`}
|
|
||||||
name={[field.name, "amount"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<CurrencyInput min={0} />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label={
|
|
||||||
<div>
|
|
||||||
{t("jobs.fields.dms.payer.controlnumber")}{" "}
|
|
||||||
<Dropdown
|
|
||||||
overlay={
|
|
||||||
<Menu>
|
|
||||||
{bodyshop.cdk_configuration.controllist &&
|
|
||||||
bodyshop.cdk_configuration.controllist.map(
|
|
||||||
(key, idx) => (
|
|
||||||
<Menu.Item
|
|
||||||
key={idx}
|
|
||||||
onClick={() => {
|
|
||||||
form.setFieldsValue({
|
|
||||||
payers: form
|
|
||||||
.getFieldValue("payers")
|
|
||||||
.map((row, mapIndex) => {
|
|
||||||
if (index !== mapIndex)
|
|
||||||
return row;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...row,
|
|
||||||
controlnumber:
|
|
||||||
key.controlnumber,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{key.name}
|
|
||||||
</Menu.Item>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Menu>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
|
||||||
<DownOutlined />
|
|
||||||
</a>
|
|
||||||
</Dropdown>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
key={`${index}controlnumber`}
|
|
||||||
name={[field.name, "controlnumber"]}
|
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
},
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input />
|
<Input/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="kmin"
|
||||||
|
label={t("jobs.fields.kmin")}
|
||||||
|
initialValue={job && job.kmin}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber disabled/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="kmout"
|
||||||
|
label={t("jobs.fields.kmout")}
|
||||||
|
initialValue={job && job.kmout}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber disabled/>
|
||||||
|
</Form.Item>
|
||||||
|
</LayoutFormRow>
|
||||||
|
|
||||||
<Form.Item shouldUpdate>
|
{bodyshop.cdk_dealerid && (
|
||||||
{() => {
|
<div>
|
||||||
const payers = form.getFieldValue("payers");
|
<LayoutFormRow style={{justifyContent: "center"}} grow>
|
||||||
|
<Form.Item
|
||||||
const row = payers && payers[index];
|
name="dms_make"
|
||||||
|
label={t("jobs.fields.dms.dms_make")}
|
||||||
const cdkPayer =
|
rules={[
|
||||||
bodyshop.cdk_configuration.payers &&
|
{
|
||||||
bodyshop.cdk_configuration.payers.find(
|
required: true,
|
||||||
(i) => i && row && i.name === row.name
|
},
|
||||||
);
|
]}
|
||||||
if (
|
>
|
||||||
i18n.exists(`jobs.fields.${cdkPayer?.control_type}`)
|
<Input disabled/>
|
||||||
)
|
</Form.Item>
|
||||||
return (
|
<Form.Item
|
||||||
<div>
|
name="dms_model"
|
||||||
{cdkPayer &&
|
label={t("jobs.fields.dms.dms_model")}
|
||||||
t(`jobs.fields.${cdkPayer?.control_type}`)}
|
rules={[
|
||||||
</div>
|
{
|
||||||
);
|
required: true,
|
||||||
else if (
|
},
|
||||||
i18n.exists(
|
]}
|
||||||
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
|
>
|
||||||
)
|
<Input disabled/>
|
||||||
) {
|
</Form.Item>
|
||||||
return (
|
<Form.Item
|
||||||
<div>
|
name="inservicedate"
|
||||||
{cdkPayer &&
|
label={t("jobs.fields.dms.inservicedate")}
|
||||||
t(
|
>
|
||||||
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
|
<FormDatePicker/>
|
||||||
)}
|
</Form.Item>
|
||||||
</div>
|
</LayoutFormRow>
|
||||||
);
|
<Space>
|
||||||
} else {
|
<DmsCdkMakes form={form} socket={socket} job={job}/>
|
||||||
return null;
|
<DmsCdkMakesRefetch/>
|
||||||
}
|
<Form.Item
|
||||||
}}
|
name="dms_unsold"
|
||||||
</Form.Item>
|
label={t("jobs.fields.dms.dms_unsold")}
|
||||||
|
initialValue={false}
|
||||||
<DeleteFilled
|
>
|
||||||
onClick={() => {
|
<Switch/>
|
||||||
remove(field.name);
|
</Form.Item>
|
||||||
}}
|
<Form.Item
|
||||||
/>
|
name="dms_model_override"
|
||||||
</Space>
|
label={t("jobs.fields.dms.dms_model_override")}
|
||||||
</Form.Item>
|
initialValue={false}
|
||||||
))}
|
>
|
||||||
<Form.Item>
|
<Switch/>
|
||||||
<Button
|
</Form.Item>
|
||||||
disabled={!(fields.length < 3)}
|
</Space>
|
||||||
onClick={() => {
|
</div>
|
||||||
if (fields.length < 3) add();
|
)}
|
||||||
}}
|
<Form.Item
|
||||||
style={{ width: "100%" }}
|
name="story"
|
||||||
>
|
label={t("jobs.fields.dms.story")}
|
||||||
{t("jobs.actions.dms.addpayer")}
|
rules={[
|
||||||
</Button>
|
{
|
||||||
</Form.Item>
|
required: true,
|
||||||
</div>
|
},
|
||||||
);
|
]}
|
||||||
}}
|
|
||||||
</Form.List>
|
|
||||||
<Form.Item shouldUpdate>
|
|
||||||
{() => {
|
|
||||||
//Perform Calculation to determine discrepancy.
|
|
||||||
let totalAllocated = Dinero();
|
|
||||||
|
|
||||||
const payers = form.getFieldValue("payers");
|
|
||||||
payers &&
|
|
||||||
payers.forEach((payer) => {
|
|
||||||
totalAllocated = totalAllocated.add(
|
|
||||||
Dinero({ amount: Math.round((payer?.amount || 0) * 100) })
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const totals =
|
|
||||||
socket.allocationsSummary &&
|
|
||||||
socket.allocationsSummary.reduce(
|
|
||||||
(acc, val) => {
|
|
||||||
return {
|
|
||||||
totalSale: acc.totalSale.add(Dinero(val.sale)),
|
|
||||||
totalCost: acc.totalCost.add(Dinero(val.cost)),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
totalSale: Dinero(),
|
|
||||||
totalCost: Dinero(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const discrep = totals
|
|
||||||
? totals.totalSale.subtract(totalAllocated)
|
|
||||||
: Dinero();
|
|
||||||
return (
|
|
||||||
<Space size="large" wrap align="center">
|
|
||||||
<Statistic
|
|
||||||
title={t("jobs.labels.subtotal")}
|
|
||||||
value={(totals ? totals.totalSale : Dinero()).toFormat()}
|
|
||||||
/>
|
|
||||||
<Typography.Title>-</Typography.Title>
|
|
||||||
<Statistic
|
|
||||||
title={t("jobs.labels.dms.totalallocated")}
|
|
||||||
value={totalAllocated.toFormat()}
|
|
||||||
/>
|
|
||||||
<Typography.Title>=</Typography.Title>
|
|
||||||
<Statistic
|
|
||||||
title={t("jobs.labels.dms.notallocated")}
|
|
||||||
valueStyle={{
|
|
||||||
color: discrep.getAmount() === 0 ? "green" : "red",
|
|
||||||
}}
|
|
||||||
value={discrep.toFormat()}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
disabled={
|
|
||||||
!socket.allocationsSummary || discrep.getAmount() !== 0
|
|
||||||
}
|
|
||||||
htmlType="submit"
|
|
||||||
>
|
>
|
||||||
{t("jobs.actions.dms.post")}
|
<Input.TextArea maxLength={240}/>
|
||||||
</Button>
|
</Form.Item>
|
||||||
</Space>
|
|
||||||
);
|
<Divider/>
|
||||||
}}
|
<Space size="large" wrap align="center">
|
||||||
</Form.Item>
|
<Statistic
|
||||||
</Form>
|
title={t("jobs.fields.ded_amt")}
|
||||||
</Card>
|
value={Dinero(
|
||||||
);
|
job.job_totals.totals.custPayable.deductible
|
||||||
|
).toFormat()}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("jobs.labels.total_cust_payable")}
|
||||||
|
value={Dinero(job.job_totals.totals.custPayable.total).toFormat()}
|
||||||
|
/>
|
||||||
|
<Statistic
|
||||||
|
title={t("jobs.labels.net_repairs")}
|
||||||
|
value={Dinero(job.job_totals.totals.net_repairs).toFormat()}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Form.List name={["payers"]}>
|
||||||
|
{(fields, {add, remove}) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<Form.Item key={field.key}>
|
||||||
|
<Space wrap>
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.dms.payer.name")}
|
||||||
|
key={`${index}name`}
|
||||||
|
name={[field.name, "name"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
style={{minWidth: "15rem"}}
|
||||||
|
onSelect={(value) => handlePayerSelect(value, index)}
|
||||||
|
>
|
||||||
|
{bodyshop.cdk_configuration &&
|
||||||
|
bodyshop.cdk_configuration.payers &&
|
||||||
|
bodyshop.cdk_configuration.payers.map((payer) => (
|
||||||
|
<Select.Option key={payer.name}>
|
||||||
|
{payer.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.dms.payer.dms_acctnumber")}
|
||||||
|
key={`${index}dms_acctnumber`}
|
||||||
|
name={[field.name, "dms_acctnumber"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input disabled/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.dms.payer.amount")}
|
||||||
|
key={`${index}amount`}
|
||||||
|
name={[field.name, "amount"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CurrencyInput min={0}/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={
|
||||||
|
<div>
|
||||||
|
{t("jobs.fields.dms.payer.controlnumber")}{" "}
|
||||||
|
<Dropdown menu={{
|
||||||
|
items: bodyshop.cdk_configuration.controllist?.map((key, idx) => ({
|
||||||
|
key: idx,
|
||||||
|
label: key.name,
|
||||||
|
onClick: () => {
|
||||||
|
form.setFieldsValue({
|
||||||
|
payers: form.getFieldValue("payers").map((row, mapIndex) => {
|
||||||
|
if (index !== mapIndex) return row;
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
controlnumber: key.controlnumber,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}}>
|
||||||
|
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||||
|
<DownOutlined/>
|
||||||
|
</a>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
key={`${index}controlnumber`}
|
||||||
|
name={[field.name, "controlnumber"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item shouldUpdate>
|
||||||
|
{() => {
|
||||||
|
const payers = form.getFieldValue("payers");
|
||||||
|
|
||||||
|
const row = payers && payers[index];
|
||||||
|
|
||||||
|
const cdkPayer =
|
||||||
|
bodyshop.cdk_configuration.payers &&
|
||||||
|
bodyshop.cdk_configuration.payers.find(
|
||||||
|
(i) => i && row && i.name === row.name
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
i18n.exists(`jobs.fields.${cdkPayer?.control_type}`)
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{cdkPayer &&
|
||||||
|
t(`jobs.fields.${cdkPayer?.control_type}`)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
else if (
|
||||||
|
i18n.exists(
|
||||||
|
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{cdkPayer &&
|
||||||
|
t(
|
||||||
|
`jobs.fields.dms.control_type.${cdkPayer?.control_type}`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<DeleteFilled
|
||||||
|
onClick={() => {
|
||||||
|
remove(field.name);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
))}
|
||||||
|
<Form.Item>
|
||||||
|
<Button
|
||||||
|
disabled={!(fields.length < 3)}
|
||||||
|
onClick={() => {
|
||||||
|
if (fields.length < 3) add();
|
||||||
|
}}
|
||||||
|
style={{width: "100%"}}
|
||||||
|
>
|
||||||
|
{t("jobs.actions.dms.addpayer")}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.List>
|
||||||
|
<Form.Item shouldUpdate>
|
||||||
|
{() => {
|
||||||
|
//Perform Calculation to determine discrepancy.
|
||||||
|
let totalAllocated = Dinero();
|
||||||
|
|
||||||
|
const payers = form.getFieldValue("payers");
|
||||||
|
payers &&
|
||||||
|
payers.forEach((payer) => {
|
||||||
|
totalAllocated = totalAllocated.add(
|
||||||
|
Dinero({amount: Math.round((payer?.amount || 0) * 100)})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const totals =
|
||||||
|
socket.allocationsSummary &&
|
||||||
|
socket.allocationsSummary.reduce(
|
||||||
|
(acc, val) => {
|
||||||
|
return {
|
||||||
|
totalSale: acc.totalSale.add(Dinero(val.sale)),
|
||||||
|
totalCost: acc.totalCost.add(Dinero(val.cost)),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
totalSale: Dinero(),
|
||||||
|
totalCost: Dinero(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const discrep = totals
|
||||||
|
? totals.totalSale.subtract(totalAllocated)
|
||||||
|
: Dinero();
|
||||||
|
return (
|
||||||
|
<Space size="large" wrap align="center">
|
||||||
|
<Statistic
|
||||||
|
title={t("jobs.labels.subtotal")}
|
||||||
|
value={(totals ? totals.totalSale : Dinero()).toFormat()}
|
||||||
|
/>
|
||||||
|
<Typography.Title>-</Typography.Title>
|
||||||
|
<Statistic
|
||||||
|
title={t("jobs.labels.dms.totalallocated")}
|
||||||
|
value={totalAllocated.toFormat()}
|
||||||
|
/>
|
||||||
|
<Typography.Title>=</Typography.Title>
|
||||||
|
<Statistic
|
||||||
|
title={t("jobs.labels.dms.notallocated")}
|
||||||
|
valueStyle={{
|
||||||
|
color: discrep.getAmount() === 0 ? "green" : "red",
|
||||||
|
}}
|
||||||
|
value={discrep.toFormat()}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
!socket.allocationsSummary || discrep.getAmount() !== 0
|
||||||
|
}
|
||||||
|
htmlType="submit"
|
||||||
|
>
|
||||||
|
{t("jobs.actions.dms.post")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,9 +60,10 @@ export function DocumentEditorComponent({ currentUser, bodyshop, document }) {
|
|||||||
markerArea.current = new markerjs2.MarkerArea(imgRef.current);
|
markerArea.current = new markerjs2.MarkerArea(imgRef.current);
|
||||||
|
|
||||||
// attach an event handler to assign annotated image back to our image element
|
// attach an event handler to assign annotated image back to our image element
|
||||||
markerArea.current.addCloseEventListener((closeEvent) => {});
|
markerArea.current.addEventListener("close", (closeEvent) => {});
|
||||||
|
|
||||||
markerArea.current.addRenderEventListener((dataUrl) => {
|
markerArea.current.addEventListener("render", (event) => {
|
||||||
|
const dataUrl = event.dataUrl;
|
||||||
imgRef.current.src = dataUrl;
|
imgRef.current.src = dataUrl;
|
||||||
markerArea.current.close();
|
markerArea.current.close();
|
||||||
triggerUpload(dataUrl);
|
triggerUpload(dataUrl);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import queryString from "query-string";
|
|||||||
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 { connect } from "react-redux";
|
||||||
import { useLocation } from "react-router";
|
import { useLocation } from "react-router-dom";
|
||||||
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
|
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
|
||||||
import { GET_DOCUMENT_BY_PK } from "../../graphql/documents.queries";
|
import { GET_DOCUMENT_BY_PK } from "../../graphql/documents.queries";
|
||||||
import { setBodyshop } from "../../redux/user/user.actions";
|
import { setBodyshop } from "../../redux/user/user.actions";
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export const uploadToCloudinary = async (
|
|||||||
let tags = `${bodyshop.imexshopid},${
|
let tags = `${bodyshop.imexshopid},${
|
||||||
tagsArray ? tagsArray.map((tag) => `${tag},`) : ""
|
tagsArray ? tagsArray.map((tag) => `${tag},`) : ""
|
||||||
}`;
|
}`;
|
||||||
// let eager = process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS;
|
// let eager = import.meta.env.VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS;
|
||||||
|
|
||||||
//Get the signed url.
|
//Get the signed url.
|
||||||
|
|
||||||
@@ -96,14 +96,14 @@ export const uploadToCloudinary = async (
|
|||||||
|
|
||||||
formData.append("upload_preset", upload_preset);
|
formData.append("upload_preset", upload_preset);
|
||||||
|
|
||||||
formData.append("api_key", process.env.REACT_APP_CLOUDINARY_API_KEY);
|
formData.append("api_key", import.meta.env.VITE_APP_CLOUDINARY_API_KEY);
|
||||||
formData.append("public_id", public_id);
|
formData.append("public_id", public_id);
|
||||||
formData.append("tags", tags);
|
formData.append("tags", tags);
|
||||||
formData.append("timestamp", timestamp);
|
formData.append("timestamp", timestamp);
|
||||||
formData.append("signature", signature);
|
formData.append("signature", signature);
|
||||||
|
|
||||||
const cloudinaryUploadResponse = await cleanAxios.post(
|
const cloudinaryUploadResponse = await cleanAxios.post(
|
||||||
`${process.env.REACT_APP_CLOUDINARY_ENDPOINT_API}/${DetermineFileType(
|
`${import.meta.env.VITE_APP_CLOUDINARY_ENDPOINT_API}/${DetermineFileType(
|
||||||
fileType
|
fileType
|
||||||
)}/upload`,
|
)}/upload`,
|
||||||
formData,
|
formData,
|
||||||
|
|||||||
@@ -1,267 +1,262 @@
|
|||||||
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
|
import {UploadOutlined, UserAddOutlined} from "@ant-design/icons";
|
||||||
import {
|
import {Button, Divider, Dropdown, Form, Input, Select, Space, Tabs, Upload,} from "antd";
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
Dropdown,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
Menu,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
Tabs,
|
|
||||||
Upload,
|
|
||||||
} from "antd";
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
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";
|
||||||
import { createStructuredSelector } from "reselect";
|
import {createStructuredSelector} from "reselect";
|
||||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
import {selectEmailConfig} from "../../redux/email/email.selectors";
|
||||||
import {
|
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||||
selectBodyshop,
|
import {CreateExplorerLinkForJob} from "../../utils/localmedia";
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
|
|
||||||
import EmailDocumentsComponent from "../email-documents/email-documents.component";
|
import EmailDocumentsComponent from "../email-documents/email-documents.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
emailConfig: selectEmailConfig,
|
emailConfig: selectEmailConfig,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(EmailOverlayComponent);
|
)(EmailOverlayComponent);
|
||||||
|
|
||||||
export function EmailOverlayComponent({
|
export function EmailOverlayComponent({
|
||||||
emailConfig,
|
emailConfig,
|
||||||
form,
|
form,
|
||||||
selectedMediaState,
|
selectedMediaState,
|
||||||
bodyshop,
|
bodyshop,
|
||||||
currentUser,
|
currentUser,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation();
|
||||||
const handleClick = ({ item, key, keyPath }) => {
|
const handleClick = ({item, key, keyPath}) => {
|
||||||
const email = item.props.value;
|
const email = item.props.value;
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
to: _.uniq([
|
to: _.uniq([
|
||||||
...form.getFieldValue("to"),
|
...form.getFieldValue("to"),
|
||||||
...(typeof email === "string" ? [email] : email),
|
...(typeof email === "string" ? [email] : email),
|
||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const handle_CC_Click = ({ item, key, keyPath }) => {
|
const handle_CC_Click = ({item, key, keyPath}) => {
|
||||||
const email = item.props.value;
|
const email = item.props.value;
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
cc: _.uniq([
|
cc: _.uniq([
|
||||||
...(form.getFieldValue("cc") || ""),
|
...(form.getFieldValue("cc") || ""),
|
||||||
...(typeof email === "string" ? [email] : email),
|
...(typeof email === "string" ? [email] : email),
|
||||||
]),
|
]),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const menu = (
|
const emailsToMenu = {
|
||||||
<div>
|
items: [
|
||||||
<Menu onClick={handleClick}>
|
...bodyshop.employees
|
||||||
{bodyshop.employees
|
.filter((e) => e.user_email)
|
||||||
.filter((e) => e.user_email)
|
.map((e, idx) => ({
|
||||||
.map((e, idx) => (
|
key: idx,
|
||||||
<Menu.Item value={e.user_email} key={idx}>
|
label: `${e.first_name} ${e.last_name}`,
|
||||||
{`${e.first_name} ${e.last_name}`}
|
value: e.user_email,
|
||||||
</Menu.Item>
|
})),
|
||||||
))}
|
...bodyshop.md_to_emails.map((e, idx) => ({
|
||||||
{bodyshop.md_to_emails.map((e, idx) => (
|
key: idx + "group",
|
||||||
<Menu.Item value={e.emails} key={idx + "group"}>
|
label: e.label,
|
||||||
{e.label}
|
value: e.emails,
|
||||||
</Menu.Item>
|
})),
|
||||||
))}
|
],
|
||||||
</Menu>
|
onClick: handleClick,
|
||||||
</div>
|
};
|
||||||
);
|
const menuCC = {
|
||||||
|
items: [
|
||||||
|
...bodyshop.employees
|
||||||
|
.filter((e) => e.user_email)
|
||||||
|
.map((e, idx) => ({
|
||||||
|
key: idx,
|
||||||
|
label: `${e.first_name} ${e.last_name}`,
|
||||||
|
value: e.user_email,
|
||||||
|
})),
|
||||||
|
...bodyshop.md_to_emails.map((e, idx) => ({
|
||||||
|
key: idx + "group",
|
||||||
|
label: e.label,
|
||||||
|
value: e.emails,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
onClick: handle_CC_Click,
|
||||||
|
};
|
||||||
|
|
||||||
const menuCC = (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Menu onClick={handle_CC_Click}>
|
<Form.Item
|
||||||
{bodyshop.employees
|
label={t("emails.fields.from")}
|
||||||
.filter((e) => e.user_email)
|
name="from"
|
||||||
.map((e, idx) => (
|
rules={[
|
||||||
<Menu.Item value={e.user_email} key={idx}>
|
{
|
||||||
{`${e.first_name} ${e.last_name}`}
|
required: true,
|
||||||
</Menu.Item>
|
//message: t("general.validation.required"),
|
||||||
))}
|
},
|
||||||
{bodyshop.md_to_emails.map((e, idx) => (
|
]}
|
||||||
<Menu.Item value={e.emails} key={idx + "group"}>
|
|
||||||
{e.label}
|
|
||||||
</Menu.Item>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Form.Item
|
|
||||||
label={t("emails.fields.from")}
|
|
||||||
name="from"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select>
|
|
||||||
<Select.Option key={currentUser.email}>
|
|
||||||
{currentUser.email}
|
|
||||||
</Select.Option>
|
|
||||||
<Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
|
|
||||||
{bodyshop.md_from_emails &&
|
|
||||||
bodyshop.md_from_emails.map((e) => (
|
|
||||||
<Select.Option key={e}>{e}</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={
|
|
||||||
<Space>
|
|
||||||
{t("emails.fields.to")}
|
|
||||||
<Dropdown overlay={menu}>
|
|
||||||
<a
|
|
||||||
className="ant-dropdown-link"
|
|
||||||
href=" #"
|
|
||||||
onClick={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<UserAddOutlined />
|
|
||||||
</a>
|
|
||||||
</Dropdown>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
name="to"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={
|
|
||||||
<Space>
|
|
||||||
{t("emails.fields.cc")}
|
|
||||||
<Dropdown overlay={menuCC}>
|
|
||||||
<a
|
|
||||||
className="ant-dropdown-link"
|
|
||||||
href=" #"
|
|
||||||
onClick={(e) => e.preventDefault()}
|
|
||||||
>
|
|
||||||
<UserAddOutlined />
|
|
||||||
</a>
|
|
||||||
</Dropdown>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
name="cc"
|
|
||||||
>
|
|
||||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("emails.fields.subject")}
|
|
||||||
name="subject"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Divider>{t("emails.labels.preview")}</Divider>
|
|
||||||
{bodyshop.attach_pdf_to_email && (
|
|
||||||
<strong>{t("emails.labels.pdfcopywillbeattached")}</strong>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Form.Item shouldUpdate>
|
|
||||||
{() => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
padding: "1rem",
|
|
||||||
|
|
||||||
backgroundColor: "lightgray",
|
|
||||||
borderLeft: "6px solid #2196F3",
|
|
||||||
}}
|
|
||||||
dangerouslySetInnerHTML={{ __html: form.getFieldValue("html") }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Tabs>
|
|
||||||
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
|
|
||||||
<EmailDocumentsComponent
|
|
||||||
selectedMediaState={selectedMediaState}
|
|
||||||
form={form}
|
|
||||||
/>
|
|
||||||
</Tabs.TabPane>
|
|
||||||
|
|
||||||
<Tabs.TabPane tab={t("emails.labels.attachments")} key="attachments">
|
|
||||||
{bodyshop.uselocalmediaserver && emailConfig.jobid && (
|
|
||||||
<a href={CreateExplorerLinkForJob({ jobid: emailConfig.jobid })}>
|
|
||||||
<Button>{t("documents.labels.openinexplorer")}</Button>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
<Form.Item
|
|
||||||
name="fileList"
|
|
||||||
valuePropName="fileList"
|
|
||||||
getValueFromEvent={(e) => {
|
|
||||||
if (Array.isArray(e)) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
return e && e.fileList;
|
|
||||||
}}
|
|
||||||
rules={[
|
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
const totalSize = value.reduce(
|
|
||||||
(acc, val) => (acc = acc + val.size),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
const limit =
|
|
||||||
10485760 - new Blob([form.getFieldValue("html")]).size;
|
|
||||||
|
|
||||||
if (totalSize > limit) {
|
|
||||||
return Promise.reject(t("general.errors.sizelimit"));
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Upload.Dragger
|
|
||||||
beforeUpload={Upload.LIST_IGNORE}
|
|
||||||
multiple
|
|
||||||
listType="picture-card"
|
|
||||||
>
|
>
|
||||||
<>
|
<Select>
|
||||||
<p className="ant-upload-drag-icon">
|
<Select.Option key={currentUser.email}>
|
||||||
<UploadOutlined />
|
{currentUser.email}
|
||||||
</p>
|
</Select.Option>
|
||||||
<p className="ant-upload-text">
|
<Select.Option key={bodyshop.email}>{bodyshop.email}</Select.Option>
|
||||||
Click or drag files to this area to upload.
|
{bodyshop.md_from_emails &&
|
||||||
</p>
|
bodyshop.md_from_emails.map((e) => (
|
||||||
</>
|
<Select.Option key={e}>{e}</Select.Option>
|
||||||
</Upload.Dragger>
|
))}
|
||||||
</Form.Item>
|
</Select>
|
||||||
</Tabs.TabPane>
|
</Form.Item>
|
||||||
</Tabs>
|
<Form.Item
|
||||||
</div>
|
label={
|
||||||
);
|
<Space>
|
||||||
|
{t("emails.fields.to")}
|
||||||
|
<Dropdown menu={emailsToMenu}>
|
||||||
|
<a
|
||||||
|
className="ant-dropdown-link"
|
||||||
|
href=" #"
|
||||||
|
onClick={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<UserAddOutlined/>
|
||||||
|
</a>
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
name="to"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select mode="tags" tokenSeparators={[",", ";"]}/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={
|
||||||
|
<Space>
|
||||||
|
{t("emails.fields.cc")}
|
||||||
|
<Dropdown menu={menuCC}>
|
||||||
|
<a
|
||||||
|
className="ant-dropdown-link"
|
||||||
|
href=" #"
|
||||||
|
onClick={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
|
<UserAddOutlined/>
|
||||||
|
</a>
|
||||||
|
</Dropdown>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
name="cc"
|
||||||
|
>
|
||||||
|
<Select mode="tags" tokenSeparators={[",", ";"]}/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("emails.fields.subject")}
|
||||||
|
name="subject"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Divider>{t("emails.labels.preview")}</Divider>
|
||||||
|
{bodyshop.attach_pdf_to_email && (
|
||||||
|
<strong>{t("emails.labels.pdfcopywillbeattached")}</strong>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Form.Item shouldUpdate>
|
||||||
|
{() => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "1rem",
|
||||||
|
|
||||||
|
backgroundColor: "lightgray",
|
||||||
|
borderLeft: "6px solid #2196F3",
|
||||||
|
}}
|
||||||
|
dangerouslySetInnerHTML={{__html: form.getFieldValue("html")}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="documents"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: "documents",
|
||||||
|
label: t("emails.labels.documents"),
|
||||||
|
children: (
|
||||||
|
<EmailDocumentsComponent
|
||||||
|
selectedMediaState={selectedMediaState}
|
||||||
|
form={form}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "attachments",
|
||||||
|
label: t("emails.labels.attachments"),
|
||||||
|
children: (
|
||||||
|
<>
|
||||||
|
{bodyshop.uselocalmediaserver && emailConfig.jobid && (
|
||||||
|
<a href={CreateExplorerLinkForJob({jobid: emailConfig.jobid})}>
|
||||||
|
<Button>{t("documents.labels.openinexplorer")}</Button>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
<Form.Item
|
||||||
|
name="fileList"
|
||||||
|
valuePropName="fileList"
|
||||||
|
getValueFromEvent={(e) => {
|
||||||
|
if (Array.isArray(e)) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
return e && e.fileList;
|
||||||
|
}}
|
||||||
|
rules={[
|
||||||
|
({getFieldValue}) => ({
|
||||||
|
validator(rule, value) {
|
||||||
|
const totalSize = value.reduce(
|
||||||
|
(acc, val) => (acc = acc + val.size),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
const limit =
|
||||||
|
10485760 - new Blob([form.getFieldValue("html")]).size;
|
||||||
|
|
||||||
|
if (totalSize > limit) {
|
||||||
|
return Promise.reject(t("general.errors.sizelimit"));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Upload.Dragger
|
||||||
|
beforeUpload={Upload.LIST_IGNORE}
|
||||||
|
multiple
|
||||||
|
listType="picture-card"
|
||||||
|
>
|
||||||
|
<>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<UploadOutlined/>
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">
|
||||||
|
Click or drag files to this area to upload.
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
</Upload.Dragger>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export function EmailOverlayContainer({
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
destroyOnClose={true}
|
destroyOnClose={true}
|
||||||
visible={modalVisible}
|
open={modalVisible}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
width={"80%"}
|
width={"80%"}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
@@ -186,7 +186,6 @@ export function EmailOverlayContainer({
|
|||||||
okText={t("general.actions.send")}
|
okText={t("general.actions.send")}
|
||||||
okButtonProps={{
|
okButtonProps={{
|
||||||
loading: sending,
|
loading: sending,
|
||||||
|
|
||||||
disabled:
|
disabled:
|
||||||
selectedMedia &&
|
selectedMedia &&
|
||||||
(selectedMedia
|
(selectedMedia
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { selectCurrentUser } from "../../redux/user/user.selectors";
|
|||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import moment from "moment";
|
import dayjs from "../../utils/day";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -27,16 +27,16 @@ export function EmailTestComponent({ currentUser, setEmailOptions }) {
|
|||||||
variables: {
|
variables: {
|
||||||
...(values.start
|
...(values.start
|
||||||
? {
|
? {
|
||||||
start: moment(values.start).startOf("day").format("YYYY-MM-DD"),
|
start: dayjs(values.start).startOf("day").format("YYYY-MM-DD"),
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(values.end
|
...(values.end
|
||||||
? { end: moment(values.end).endOf("day").format("YYYY-MM-DD") }
|
? { end: dayjs(values.end).endOf("day").format("YYYY-MM-DD") }
|
||||||
: {}),
|
: {}),
|
||||||
...(values.start
|
...(values.start
|
||||||
? { starttz: moment(values.start).startOf("day") }
|
? { starttz: dayjs(values.start).startOf("day") }
|
||||||
: {}),
|
: {}),
|
||||||
...(values.end ? { endtz: moment(values.end).endOf("day") } : {}),
|
...(values.end ? { endtz: dayjs(values.end).endOf("day") } : {}),
|
||||||
|
|
||||||
...(values.id ? { id: values.id } : {}),
|
...(values.id ? { id: values.id } : {}),
|
||||||
},
|
},
|
||||||
|
|||||||
250
client/src/components/eula/eula.component.jsx
Normal file
250
client/src/components/eula/eula.component.jsx
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
import React, {useCallback, useEffect, useRef, useState} from "react";
|
||||||
|
import {Button, Card, Checkbox, Col, Form, Input, Modal, notification, Row} from "antd";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
import {createStructuredSelector} from "reselect";
|
||||||
|
import {selectCurrentEula, selectCurrentUser} from "../../redux/user/user.selectors";
|
||||||
|
import {connect} from "react-redux";
|
||||||
|
import {FormDatePicker} from "../form-date-picker/form-date-picker.component";
|
||||||
|
import {INSERT_EULA_ACCEPTANCE} from "../../graphql/user.queries";
|
||||||
|
import {useMutation} from "@apollo/client";
|
||||||
|
import {acceptEula} from "../../redux/user/user.actions";
|
||||||
|
import {useTranslation} from "react-i18next";
|
||||||
|
import day from '../../utils/day';
|
||||||
|
|
||||||
|
import './eula.styles.scss';
|
||||||
|
|
||||||
|
const Eula = ({currentEula, currentUser, acceptEula}) => {
|
||||||
|
const [formReady, setFormReady] = useState(false);
|
||||||
|
const [hasEverScrolledToBottom, setHasEverScrolledToBottom] = useState(false);
|
||||||
|
const [insertEulaAcceptance] = useMutation(INSERT_EULA_ACCEPTANCE);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const markdownCardRef = useRef(null);
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const [api, contextHolder] = notification.useNotification();
|
||||||
|
|
||||||
|
const handleScroll = useCallback((e) => {
|
||||||
|
const bottom = e.target.scrollHeight - 100 <= e.target.scrollTop + e.target.clientHeight;
|
||||||
|
if (bottom && !hasEverScrolledToBottom) {
|
||||||
|
setHasEverScrolledToBottom(true);
|
||||||
|
} else if (e.target.scrollHeight <= e.target.clientHeight && !hasEverScrolledToBottom) {
|
||||||
|
setHasEverScrolledToBottom(true);
|
||||||
|
}
|
||||||
|
}, [hasEverScrolledToBottom, setHasEverScrolledToBottom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleScroll({target: markdownCardRef.current});
|
||||||
|
}, [handleScroll]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(() => {
|
||||||
|
form.validateFields({validateOnly: true})
|
||||||
|
.then(() => setFormReady(hasEverScrolledToBottom))
|
||||||
|
.catch(() => setFormReady(false));
|
||||||
|
}, [form, hasEverScrolledToBottom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleChange();
|
||||||
|
}, [handleChange, hasEverScrolledToBottom, form]);
|
||||||
|
|
||||||
|
const onFinish = async ({acceptTerms, ...formValues}) => {
|
||||||
|
const eulaId = currentEula.id;
|
||||||
|
const useremail = currentUser.email;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {accepted_terms, ...otherFormValues} = formValues;
|
||||||
|
|
||||||
|
// Trim the values of the fields before submitting
|
||||||
|
const trimmedFormValues = Object.entries(otherFormValues).reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = typeof value === 'string' ? value.trim() : value;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
await insertEulaAcceptance({
|
||||||
|
variables: {
|
||||||
|
eulaAcceptance: {
|
||||||
|
eulaid: eulaId,
|
||||||
|
useremail,
|
||||||
|
...otherFormValues,
|
||||||
|
...trimmedFormValues,
|
||||||
|
date_accepted: new Date(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
acceptEula();
|
||||||
|
} catch (err) {
|
||||||
|
api.error({
|
||||||
|
message: t('eula.errors.acceptance.message'),
|
||||||
|
description: t('eula.errors.acceptance.description'),
|
||||||
|
placement: 'bottomRight',
|
||||||
|
duration: 5000,
|
||||||
|
|
||||||
|
});
|
||||||
|
console.log(`${t('eula.errors.acceptance.message')}`);
|
||||||
|
console.dir({
|
||||||
|
message: err.message,
|
||||||
|
stack: err.stack,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{contextHolder}
|
||||||
|
<Modal
|
||||||
|
title={t('eula.titles.modal')}
|
||||||
|
className='eula-modal'
|
||||||
|
width={'100vh'}
|
||||||
|
open={currentEula}
|
||||||
|
footer={() => (
|
||||||
|
<Button
|
||||||
|
className='eula-accept-button'
|
||||||
|
form='tosForm'
|
||||||
|
type="primary"
|
||||||
|
size='large'
|
||||||
|
htmlType="submit"
|
||||||
|
disabled={!formReady}
|
||||||
|
children={t('eula.buttons.accept')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
closable={false}
|
||||||
|
>
|
||||||
|
<Card type='inner' className='eula-markdown-card' onScroll={handleScroll} ref={markdownCardRef}>
|
||||||
|
<div id='markdowndiv' className='eula-markdown-div'>
|
||||||
|
<Markdown children={currentEula?.content?.replace(/\\n|\\r|\\n\\r|\\r\\n/g, '\n')}/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<EulaFormComponent form={form} handleChange={handleChange} onFinish={onFinish} t={t}/>
|
||||||
|
|
||||||
|
{!hasEverScrolledToBottom && (
|
||||||
|
<Card className='eula-never-scrolled' type='inner'>
|
||||||
|
<h3>{t('eula.content.never_scrolled')}</h3>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const EulaFormComponent = ({form, handleChange, onFinish, t}) => (
|
||||||
|
<Card type='inner' title={t('eula.titles.upper_card')} style={{marginTop: '10px'}}>
|
||||||
|
<Form id='tosForm' onChange={handleChange} onFinish={onFinish} form={form}>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label={t('eula.labels.first_name')}
|
||||||
|
name="first_name"
|
||||||
|
rules={[{
|
||||||
|
required: true,
|
||||||
|
validator: (_, value) =>
|
||||||
|
value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.first_name'))),
|
||||||
|
},]}
|
||||||
|
>
|
||||||
|
<Input placeholder={t('eula.labels.first_name')}
|
||||||
|
aria-label={t('eula.labels.first_name')}/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label={t('eula.labels.last_name')}
|
||||||
|
name="last_name"
|
||||||
|
rules={[{
|
||||||
|
required: true,
|
||||||
|
validator: (_, value) =>
|
||||||
|
value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.last_name'))),
|
||||||
|
}]}
|
||||||
|
>
|
||||||
|
<Input placeholder={t('eula.labels.last_name')}
|
||||||
|
aria-label={t('eula.labels.last_name')}/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label={t('eula.labels.business_name')}
|
||||||
|
name="business_name"
|
||||||
|
rules={[{
|
||||||
|
required: true,
|
||||||
|
validator: (_, value) =>
|
||||||
|
value.trim() !== '' ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.business_name'))),
|
||||||
|
}]}
|
||||||
|
>
|
||||||
|
<Input placeholder={t('eula.labels.business_name')} aria-label={t('eula.labels.business_name')}/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label={t('eula.labels.phone_number')}
|
||||||
|
name="phone_number"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
pattern: /^(\+\d{1,2}\s?)?1?-?\.?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/,
|
||||||
|
message: t('eula.messages.phone_number'),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input placeholder={t('eula.labels.phone_number')}
|
||||||
|
aria-label={t('eula.labels.phone_number')}/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label={t('eula.labels.address')}
|
||||||
|
name="address"
|
||||||
|
>
|
||||||
|
<Input placeholder={t('eula.labels.address')} aria-label={t('eula.labels.address')}/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label={t('eula.labels.date_accepted')}
|
||||||
|
name="date_accepted"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: (_, value) => {
|
||||||
|
if (day(value).isSame(day(), 'day')) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(t('eula.messages.date_accepted')));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<FormDatePicker onChange={handleChange} onlyToday aria-label={t('eula.labels.date_accepted')}/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={24}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item
|
||||||
|
name="accepted_terms"
|
||||||
|
valuePropName="checked"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: (_, value) =>
|
||||||
|
value ? Promise.resolve() : Promise.reject(new Error(t('eula.messages.accepted_terms'))),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Checkbox aria-label={t('eula.labels.accepted_terms')}>{t('eula.labels.accepted_terms')}</Checkbox>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
currentEula: selectCurrentEula,
|
||||||
|
currentUser: selectCurrentUser,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
acceptEula: () => dispatch(acceptEula()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(Eula);
|
||||||
21
client/src/components/eula/eula.styles.scss
Normal file
21
client/src/components/eula/eula.styles.scss
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
.eula-modal {
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eula-markdown-card {
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eula-markdown-div {
|
||||||
|
padding: 0 10px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eula-never-scrolled {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.eula-accept-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
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 { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -34,7 +34,7 @@ function FeatureWrapper({
|
|||||||
export function HasFeatureAccess({ featureName, bodyshop }) {
|
export function HasFeatureAccess({ featureName, bodyshop }) {
|
||||||
return (
|
return (
|
||||||
bodyshop.features.allAccess ||
|
bodyshop.features.allAccess ||
|
||||||
moment(bodyshop.features[featureName]).isAfter(moment())
|
dayjs(bodyshop.features[featureName]).isAfter(dayjs())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user