Compare commits
6 Commits
feature/IO
...
feature/ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fc731f564 | ||
|
|
cccb411cde | ||
|
|
a2eaa3b376 | ||
|
|
4e90534e69 | ||
|
|
a9c2502f33 | ||
|
|
c056cd02de |
@@ -8,13 +8,13 @@ orbs:
|
||||
jobs:
|
||||
api-deploy:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
- image: "cimg/base:stable"
|
||||
steps:
|
||||
- checkout
|
||||
- eb/setup
|
||||
- run:
|
||||
command: |
|
||||
eb init imex-online-production-api -r ca-central-1 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
||||
eb init imex-online-production-api -r ca-central-1 -p "Node.js 16 running on 64bit Amazon Linux 2"
|
||||
eb status --verbose
|
||||
eb deploy
|
||||
eb status
|
||||
@@ -68,120 +68,6 @@ jobs:
|
||||
from: build
|
||||
to: "s3://imex-online-production/"
|
||||
- jira/notify
|
||||
|
||||
rome-api-deploy:
|
||||
docker:
|
||||
- image: "cimg/base:stable"
|
||||
steps:
|
||||
- checkout
|
||||
- eb/setup
|
||||
- run:
|
||||
command: |
|
||||
eb init romeonline-productionapi -r us-east-2 -p "Node.js 18 running on 64bit Amazon Linux 2"
|
||||
eb status --verbose
|
||||
eb deploy
|
||||
eb status
|
||||
- jira/notify
|
||||
|
||||
rome-hasura-migrate:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
parameters:
|
||||
secret:
|
||||
type: string
|
||||
default: $HASURA_PROD_SECRET
|
||||
working_directory: ~/repo/hasura
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
|
||||
rome-app-build:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
|
||||
working_directory: ~/repo/client
|
||||
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
|
||||
- restore_cache:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
- save_cache:
|
||||
name: Save Yarn Package Cache
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
- run: yarn run build
|
||||
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://rome-online-production/"
|
||||
- jira/notify
|
||||
|
||||
test-rome-hasura-migrate:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
parameters:
|
||||
secret:
|
||||
type: string
|
||||
default: $HASURA_ROME_TEST_SECRET
|
||||
working_directory: ~/repo/hasura
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
echo ${HASURA_TEST_SECRET}
|
||||
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
|
||||
test-rome-app-build:
|
||||
docker:
|
||||
- image: cimg/node:16.15.0
|
||||
|
||||
working_directory: ~/repo/client
|
||||
|
||||
steps:
|
||||
- checkout:
|
||||
path: ~/repo
|
||||
|
||||
- restore_cache:
|
||||
name: Restore Yarn Package Cache
|
||||
keys:
|
||||
- yarn-packages-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
|
||||
- save_cache:
|
||||
name: Save Yarn Package Cache
|
||||
key: yarn-packages-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
- run: yarn run build:test
|
||||
|
||||
- aws-s3/sync:
|
||||
from: build
|
||||
to: "s3://rome-online-test/"
|
||||
- jira/notify
|
||||
|
||||
|
||||
test-hasura-migrate:
|
||||
docker:
|
||||
@@ -279,19 +165,6 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- rome-api-deploy:
|
||||
filters:
|
||||
branches:
|
||||
only: rome/master
|
||||
- rome-app-build:
|
||||
filters:
|
||||
branches:
|
||||
only: rome/master
|
||||
- rome-hasura-migrate:
|
||||
secret: ${HASURA_PROD_SECRET}
|
||||
filters:
|
||||
branches:
|
||||
only: rome/master
|
||||
- test-app-build:
|
||||
filters:
|
||||
branches:
|
||||
@@ -301,15 +174,6 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only: test
|
||||
- test-rome-app-build:
|
||||
filters:
|
||||
branches:
|
||||
only: rome/test
|
||||
- test-rome-hasura-migrate:
|
||||
secret: ${HASURA_ROME_TEST_SECRET}
|
||||
filters:
|
||||
branches:
|
||||
only: rome/test
|
||||
#- admin-app-build:
|
||||
#filters:
|
||||
#branches:
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -117,6 +117,4 @@ logs/oAuthClient-log.log
|
||||
|
||||
.node-persist/**
|
||||
|
||||
/*.env.*
|
||||
.idea/*
|
||||
.idea
|
||||
/*.env.*
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,13 @@
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
REACT_APP_GA_CODE=231099835
|
||||
REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
REACT_APP_FIREBASE_CONFIG={"apiKey":"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
|
||||
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
||||
REACT_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
||||
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
REACT_APP_COUNTRY=USA
|
||||
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
@@ -1,13 +1,13 @@
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
|
||||
REACT_APP_GA_CODE=231103507
|
||||
REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
|
||||
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
|
||||
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
|
||||
REACT_APP_CLOUDINARY_API_KEY=473322739956866
|
||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
|
||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
REACT_APP_AXIOS_BASE_API_URL=https://api.romeonline.io/
|
||||
REACT_APP_REPORTS_SERVER_URL=https://reports.romeonline.io
|
||||
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
|
||||
REACT_APP_REPORTS_SERVER_URL=https://reports.imex.online
|
||||
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk
|
||||
@@ -1,14 +1,14 @@
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.romeonline.io/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.romeonline.io/v1/graphql
|
||||
REACT_APP_GA_CODE=231103507
|
||||
REACT_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql
|
||||
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql
|
||||
REACT_APP_GA_CODE=231099835
|
||||
REACT_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
|
||||
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
|
||||
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
|
||||
REACT_APP_CLOUDINARY_API_KEY=473322739956866
|
||||
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
|
||||
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||
REACT_APP_AXIOS_BASE_API_URL=https://api.test.romeonline.io/
|
||||
REACT_APP_REPORTS_SERVER_URL=https://reports.test.romeonline.io
|
||||
REACT_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
|
||||
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||
REACT_APP_IS_TEST=true
|
||||
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||
@@ -1,8 +1,33 @@
|
||||
// craco.config.js
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const CracoLessPlugin = require("craco-less");
|
||||
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
||||
|
||||
const generateRegExp = /generate\/dayjs/;
|
||||
const updateResource = (resource) => {
|
||||
resource.request = resource.request.replace("dayjs", "moment");
|
||||
};
|
||||
const plugin = "AntdMomentWebpackPlugin";
|
||||
class AntdMomentWebpackPlugin {
|
||||
constructor(options) {
|
||||
this.options = options || {};
|
||||
}
|
||||
apply(compiler) {
|
||||
compiler.hooks.normalModuleFactory.tap(plugin, (factory) => {
|
||||
factory.hooks.beforeResolve.tap(plugin, (result) => {
|
||||
if (generateRegExp.test(result.request)) {
|
||||
updateResource(result);
|
||||
}
|
||||
});
|
||||
factory.hooks.afterResolve.tap(plugin, (result) => {
|
||||
const createData = result.createData;
|
||||
if (generateRegExp.test(createData.resource)) {
|
||||
updateResource(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
@@ -12,7 +37,7 @@ module.exports = {
|
||||
authToken:
|
||||
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
||||
org: "snapt-software",
|
||||
project: "rome-online",
|
||||
project: "imexonline",
|
||||
release: process.env.REACT_APP_GIT_SHA,
|
||||
|
||||
// webpack-specific configuration
|
||||
@@ -20,39 +45,9 @@ module.exports = {
|
||||
ignore: ["node_modules", "webpack.config.js"],
|
||||
},
|
||||
},
|
||||
{
|
||||
plugin: CracoLessPlugin,
|
||||
options: {
|
||||
lessLoaderOptions: {
|
||||
lessOptions: {
|
||||
modifyVars: {
|
||||
...(process.env.NODE_ENV === "development"
|
||||
? { "@primary-color": "#B22234" }
|
||||
: {
|
||||
//"@primary-color": "#1DA57A"
|
||||
}),
|
||||
// "@primary-color": " #1890ff", // primary color for all components
|
||||
// "@link-color": "#1890ff", // link color
|
||||
// "@success-color": "#52c41a", // success state color
|
||||
// "@warning-color": "#faad14", // warning state color
|
||||
// "@error-color": "#f5222d", // error state color
|
||||
// "@font-size-base": "14px", // major text font size
|
||||
// " @heading-color": "rgba(0, 0, 0, 0.85)", // heading text color
|
||||
// "@text-color": "rgba(0, 0, 0, 0.65)", // major text color
|
||||
// "@text-color-secondary": "rgba(0, 0, 0, 0.45)", // secondary text color
|
||||
// "@disabled-color": "rgba(0, 0, 0, 0.25)", // disable state color
|
||||
// "@border-radius-base": "2px", // major border radius
|
||||
// "@border-color-base": "#d9d9d9", // major border color
|
||||
// "@box-shadow-base":
|
||||
// "0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),0 9px 28px 8px rgba(0, 0, 0, 0.05); // major shadow for layers }",
|
||||
},
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
webpack: {
|
||||
plugins: [new AntdMomentWebpackPlugin()],
|
||||
configure: (webpackConfig) => ({
|
||||
...webpackConfig,
|
||||
optimization: {
|
||||
|
||||
57299
client/package-lock.json
generated
57299
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,21 +4,19 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:4000",
|
||||
"dependencies": {
|
||||
"@ant-design/pro-layout": "^7.6.1",
|
||||
"@apollo/client": "^3.7.9",
|
||||
"@asseinfo/react-kanban": "^2.2.0",
|
||||
"@craco/craco": "^7.0.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
||||
"@jsreport/browser-client": "^3.1.0",
|
||||
"@sentry/react": "^7.40.0",
|
||||
"@sentry/tracing": "^7.40.0",
|
||||
"@splitsoftware/splitio-react": "^1.8.1",
|
||||
"@tanem/react-nprogress": "^5.0.8",
|
||||
"antd": "^4.24.8",
|
||||
"apollo-link-logger": "^2.0.1",
|
||||
"antd": "^5.4.2",
|
||||
"apollo-link-logger": "^2.0.0",
|
||||
"axios": "^1.3.4",
|
||||
"craco-less": "^2.0.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^16.0.1",
|
||||
"enquire-js": "^0.2.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
@@ -37,28 +35,25 @@
|
||||
"phone": "^3.1.35",
|
||||
"preval.macro": "^5.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"query-string": "^7.1.3",
|
||||
"query-string": "^8.1.0",
|
||||
"rc-queue-anim": "^2.0.0",
|
||||
"rc-scroll-anim": "^2.7.6",
|
||||
"react": "^17.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-big-calendar": "^1.6.8",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-drag-listview": "^0.2.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-drag-listview": "^2.0.0",
|
||||
"react-grid-gallery": "^1.0.0",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-i18next": "^12.2.0",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-image-lightbox": "^5.1.4",
|
||||
"react-intersection-observer": "^9.4.3",
|
||||
"react-number-format": "^5.1.3",
|
||||
"react-redux": "^8.0.5",
|
||||
"react-resizable": "^3.0.4",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-sticky": "^6.0.3",
|
||||
"react-sublime-video": "^0.2.5",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"recharts": "^2.4.3",
|
||||
"redux": "^4.2.1",
|
||||
@@ -70,7 +65,7 @@
|
||||
"socket.io-client": "^4.6.1",
|
||||
"styled-components": "^5.3.6",
|
||||
"subscriptions-transport-ws": "^0.11.0",
|
||||
"web-vitals": "^2.1.4",
|
||||
"web-vitals": "^3.1.0",
|
||||
"workbox-background-sync": "^6.5.3",
|
||||
"workbox-broadcast-update": "^6.5.3",
|
||||
"workbox-cacheable-response": "^6.5.3",
|
||||
@@ -82,15 +77,15 @@
|
||||
"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"
|
||||
"workbox-streams": "^6.5.3"
|
||||
},
|
||||
"scripts": {
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"start": "craco start",
|
||||
"startrs": "react-scripts start",
|
||||
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco 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:test": "env-cmd -f .env.test yarn run build",
|
||||
"build-deploy:test": "yarn run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
|
||||
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
|
||||
"test": "cypress open",
|
||||
"eject": "react-scripts eject",
|
||||
@@ -119,9 +114,10 @@
|
||||
"react-error-overlay": "6.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/moment-webpack-plugin": "^0.0.3",
|
||||
"@sentry/webpack-plugin": "^1.20.0",
|
||||
"@testing-library/cypress": "^8.0.3",
|
||||
"cypress": "^10.3.1",
|
||||
"@testing-library/cypress": "^9.0.0",
|
||||
"cypress": "^12.3.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"react-error-overlay": "6.0.11",
|
||||
"redux-logger": "^3.0.6",
|
||||
|
||||
@@ -28,17 +28,6 @@ switch (this.location.hostname) {
|
||||
// measurementId: "${config.measurementId}",
|
||||
};
|
||||
break;
|
||||
case "romeonline.io":
|
||||
firebaseConfig = {
|
||||
apiKey: "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE",
|
||||
authDomain: "rome-prod-1.firebaseapp.com",
|
||||
projectId: "rome-prod-1",
|
||||
storageBucket: "rome-prod-1.appspot.com",
|
||||
messagingSenderId: "147786367145",
|
||||
appId: "1:147786367145:web:9d4cba68071c3f29a8a9b8",
|
||||
measurementId: "G-G8Z9DRHTZS",
|
||||
};
|
||||
break;
|
||||
case "imex.online":
|
||||
default:
|
||||
firebaseConfig = {
|
||||
|
||||
@@ -2,81 +2,23 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/ro-favicon.png" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#002366" />
|
||||
<meta name="description" content="Rome Online" />
|
||||
<meta name="description" content="ImEX Online" />
|
||||
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
||||
|
||||
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
|
||||
|
||||
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001 party="LiveChat528346"></call-us-selector>
|
||||
|
||||
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
|
||||
|
||||
<!--<call-us
|
||||
|
||||
phonesystem-url=https://rometech.east.3cx.us:5001
|
||||
|
||||
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
|
||||
|
||||
id="wp-live-chat-by-3CX"
|
||||
|
||||
minimized="true"
|
||||
|
||||
animation-style="noanimation"
|
||||
|
||||
party="LiveChat528346"
|
||||
|
||||
minimized-style="bubbleright"
|
||||
|
||||
allow-call="true"
|
||||
|
||||
allow-video="false"
|
||||
|
||||
allow-soundnotifications="true"
|
||||
|
||||
enable-mute="true"
|
||||
|
||||
enable-onmobile="true"
|
||||
|
||||
offline-enabled="true"
|
||||
|
||||
enable="true"
|
||||
|
||||
ignore-queueownership="false"
|
||||
|
||||
authentication="both"
|
||||
|
||||
show-operator-actual-name="true"
|
||||
|
||||
aknowledge-received="true"
|
||||
|
||||
gdpr-enabled="false"
|
||||
|
||||
message-userinfo-format="name"
|
||||
|
||||
message-dateformat="both"
|
||||
|
||||
lang="browser"
|
||||
|
||||
button-icon-type="default"
|
||||
|
||||
greeting-visibility="none"
|
||||
|
||||
greeting-offline-visibility="none"
|
||||
|
||||
chat-delay="2000"
|
||||
|
||||
enable-direct-call="true"
|
||||
|
||||
enable-ga="false"
|
||||
|
||||
></call-us>-->
|
||||
|
||||
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js id="tcx-callus-js" charset="utf-8"></script>
|
||||
|
||||
<link rel="apple-touch-icon" href="logo192.png" />
|
||||
<script type="text/javascript">
|
||||
window.$crisp = [];
|
||||
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
|
||||
(function () {
|
||||
d = document;
|
||||
s = d.createElement("script");
|
||||
s.src = "https://client.crisp.chat/l.js";
|
||||
s.async = 1;
|
||||
d.getElementsByTagName("head")[0].appendChild(s);
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
!(function () {
|
||||
"use strict";
|
||||
@@ -135,7 +77,7 @@ enable-ga="false"
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Rome Online</title>
|
||||
<title>ImEX Online</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"short_name": "Rome Online",
|
||||
"name": "Rome Online",
|
||||
"short_name": "ImEX Online",
|
||||
"name": "ImEX Online",
|
||||
"description": "The ultimate bodyshop management system.",
|
||||
"icons": [
|
||||
{
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 376 B |
@@ -27,12 +27,6 @@ export default function AppContainer() {
|
||||
//componentSize="small"
|
||||
input={{ autoComplete: "new-password" }}
|
||||
locale={enLocale}
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: "#326ade",
|
||||
colorInfo: "#326ade"
|
||||
},
|
||||
}}
|
||||
form={{
|
||||
validateMessages: {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
|
||||
@@ -77,9 +77,9 @@ export function App({
|
||||
if (currentUser.authorized && bodyshop) {
|
||||
client.setAttribute("imexshopid", bodyshop.imexshopid);
|
||||
|
||||
LogRocket.init("rome-online/rome-online");
|
||||
if (client.getTreatment("LogRocket_Tracking") === "on") {
|
||||
LogRocket.init("rome-online/rome-online");
|
||||
console.log("LR Start");
|
||||
LogRocket.init("gvfvfw/bodyshopapp");
|
||||
}
|
||||
}
|
||||
}, [bodyshop, client, currentUser.authorized]);
|
||||
@@ -109,7 +109,7 @@ export function App({
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Suspense fallback={<LoadingSpinner />}>
|
||||
<Suspense fallback={<LoadingSpinner message="ImEX Online" />}>
|
||||
<ErrorBoundary>
|
||||
<Route exact path="/" component={LandingPage} />
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -143,28 +143,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
//Update row highlighting on production board.
|
||||
|
||||
//Update row highlighting on production board.
|
||||
.ant-table-tbody > tr.ant-table-row:hover > td {
|
||||
background: #e7f3ff !important;
|
||||
background: #eaeaea !important;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row-selected > td {
|
||||
background: #e6f7ff !important;
|
||||
}
|
||||
|
||||
.job-line-manual {
|
||||
.job-line-manual{
|
||||
color: tomato;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
td.ant-table-column-sort {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
.rowWithColor > td {
|
||||
background-color: var(--bgColor) !important;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB |
49
client/src/components/_test/paymentMethod.jsx
Normal file
49
client/src/components/_test/paymentMethod.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||
});
|
||||
|
||||
function Test({ bodyshop, setEmailOptions }) {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setEmailOptions({
|
||||
messageOptions: {
|
||||
to: ["patrickwf@gmail.com"],
|
||||
replyTo: bodyshop.email,
|
||||
},
|
||||
template: {
|
||||
name: TemplateList().parts_order.key,
|
||||
variables: {
|
||||
id: "a7c2d4e1-f519-42a9-a071-c48cf0f22979",
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
send email
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
logImEXEvent("IMEXEVENT", { somethignArThare: 5 });
|
||||
}}
|
||||
>
|
||||
Log an ImEX Event.
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Test);
|
||||
@@ -1,63 +0,0 @@
|
||||
{
|
||||
"status": 24201299,
|
||||
"custid": 19607899,
|
||||
"paymentid": 24201299,
|
||||
"response": "A",
|
||||
"authcode": "498680",
|
||||
"declinereason": "Approved",
|
||||
"fee": 0,
|
||||
"invoice": "",
|
||||
"account": "john",
|
||||
"amount": 1000,
|
||||
"amountincludesfee": false,
|
||||
"total": 1000,
|
||||
"paymenttype": "C",
|
||||
"methodhint": "VI ***1111",
|
||||
"cardbrand": "Visa",
|
||||
"cardnumdisplay": "***1111",
|
||||
"receiptelements": {
|
||||
"authcode": "498680",
|
||||
"cust_srv_ph_num": "1-555-555-5555",
|
||||
"rcpt_pg_ftr_txt": "Thank You\nPlease Come Again",
|
||||
"rcpt_currency": "USD",
|
||||
"responsecode": "A",
|
||||
"rcpt_pay_mthd": "Visa",
|
||||
"transid": "C00 915799",
|
||||
"merch_disp_nm": "CP Devel Test",
|
||||
"rcpt_input_mthd": "Keyed",
|
||||
"rcpt_pg_hdr_txt": "Welcome!",
|
||||
"rcpt_tran_time": "Thursday February 23 2023, 11:25:36 pm +08",
|
||||
"rcpt_trans_type": "Normal Transaction (Sale)",
|
||||
"message": "Approved",
|
||||
"rcpt_dba_addr": "1234 Storefront Ave\nSome City, UT 84111",
|
||||
"avsdata": "N",
|
||||
"receiptrequirements": "S",
|
||||
"rcpt_cardnum": "************1111",
|
||||
"cv2result": "M",
|
||||
"rfnd_policy_txt": "<b>No Refunds</b>\nStore Credit Only",
|
||||
"labels": {
|
||||
"tranref": "REF#",
|
||||
"tid": "TID",
|
||||
"validationcode": "ValCode",
|
||||
"emvapplicationid": "AID",
|
||||
"emvatc": "ATC",
|
||||
"rcpt_pay_mthd": "Pay Method",
|
||||
"transid": "TransID",
|
||||
"rcpt_input_mthd": "IMode",
|
||||
"emvtsi": "TSI",
|
||||
"emvac": "AC",
|
||||
"rcpt_trans_type": "TranType",
|
||||
"emvapplicationname": "PApp",
|
||||
"visarewards": "RewardsProg"
|
||||
}
|
||||
},
|
||||
"receipttoken": "H4sIAAAAAAAAACXMTQuCMBgA4P/ynh3tw_3dBI/ipQ8NOtRN53QiblpBRfTfCzo/8LwhxGAdZCCwFYoJJFQjI2kvHdGu74lVkgmrWyWNhASW5jW7cB87yHjKKePGJODnxrrnMl7dDTKmEJlSOqV/_N30XPpyj2Eddq57_KKZ8FLzmh_G1VQnVfhjiXGK1XYTc/h8AVOkf4qUAAAA",
|
||||
"call": "card_payment",
|
||||
"nonce": "488b5568-b5c1-4f38-8b2f-3b050f3abb11P",
|
||||
"hmac": "JyPAJ9Yx0SlYBTtqns1OxAFRt+xF3l2UiLPO5zTDRBE=",
|
||||
"paymentreferenceid": "C19607899P24201299",
|
||||
"cardnum": "...1111",
|
||||
"email": "",
|
||||
"nameOnCard": "John Allen",
|
||||
"cardType": "visa"
|
||||
}
|
||||
9
client/src/components/_test/test.component.jsx
Normal file
9
client/src/components/_test/test.component.jsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from "react";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
export default function Test() {
|
||||
return (
|
||||
<div>
|
||||
<QboAuthorizeComponent />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import { Button } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setRefundPaymentContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "refund_payment" })),
|
||||
});
|
||||
|
||||
function Test({ setRefundPaymentContext, refundPaymentModal }) {
|
||||
console.log("refundPaymentModal", refundPaymentModal);
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
onClick={() =>
|
||||
setRefundPaymentContext({
|
||||
context: {},
|
||||
})
|
||||
}
|
||||
>
|
||||
Open Modal
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Test);
|
||||
@@ -15,7 +15,6 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -211,7 +210,7 @@ export function AccountingPayablesTableComponent({
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top", pageSize: pageLimit }}
|
||||
pagination={{ position: "top", pageSize: 50 }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -15,7 +15,6 @@ import PaymentExportButton from "../payment-export-button/payment-export-button.
|
||||
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
|
||||
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -210,7 +209,7 @@ export function AccountingPayablesTableComponent({
|
||||
<Table
|
||||
loading={loading}
|
||||
dataSource={dataSource}
|
||||
pagination={{ position: "top", pageSize: pageLimit }}
|
||||
pagination={{ position: "top", pageSize: 50 }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
onChange={handleTableChange}
|
||||
|
||||
@@ -61,7 +61,7 @@ export function AllocationsAssignmentComponent({
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={popContent} visible={visibility}>
|
||||
<Popover content={popContent} open={visibility}>
|
||||
<Button onClick={() => setVisibility(true)}>
|
||||
{t("allocations.actions.assign")}
|
||||
</Button>
|
||||
|
||||
@@ -59,7 +59,7 @@ export default connect(
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={popContent} visible={visibility}>
|
||||
<Popover content={popContent} open={visibility}>
|
||||
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||
{t("allocations.actions.assign")}
|
||||
</Button>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { alphaSort } from "../../utils/sorters";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function AuditTrailListComponent({ loading, data }) {
|
||||
const [state, setState] = useState({
|
||||
@@ -75,7 +74,7 @@ export default function AuditTrailListComponent({ loading, data }) {
|
||||
<Table
|
||||
{...formItemLayout}
|
||||
loading={loading}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data}
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function EmailAuditTrailListComponent({ loading, data }) {
|
||||
const [state, setState] = useState({
|
||||
@@ -54,7 +53,7 @@ export default function EmailAuditTrailListComponent({ loading, data }) {
|
||||
<Table
|
||||
{...formItemLayout}
|
||||
loading={loading}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
pagination={{ position: "top", defaultPageSize: 25 }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={data}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { DELETE_BILL } from "../../graphql/bills.queries";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
|
||||
export default function BillDeleteButton({ bill, callback }) {
|
||||
export default function BillDeleteButton({ bill }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [deleteBill] = useMutation(DELETE_BILL);
|
||||
@@ -36,8 +36,6 @@ export default function BillDeleteButton({ bill, callback }) {
|
||||
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({ message: t("bills.successes.deleted") });
|
||||
|
||||
if (callback && typeof callback === "function") callback(bill.id);
|
||||
} else {
|
||||
//Check if it's an fkey violation.
|
||||
const error = JSON.stringify(result.errors);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Form, PageHeader, Popconfirm, Space } from "antd";
|
||||
import { PageHeader } from '@ant-design/pro-layout';
|
||||
import { Button, Form, Popconfirm, Space } from "antd";
|
||||
import moment from "moment";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
@@ -178,7 +179,7 @@ export function BillDetailEditcontainer({
|
||||
<BillDetailEditReturn data={data} />
|
||||
|
||||
<Popconfirm
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onConfirm={() => form.submit()}
|
||||
onCancel={() => setVisible(false)}
|
||||
okButtonProps={{ loading: updateLoading }}
|
||||
|
||||
@@ -77,7 +77,7 @@ export function BillDetailEditReturn({
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
destroyOnClose
|
||||
title={t("bills.actions.return")}
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function BillDetailEditcontainer() {
|
||||
history.push({ search: queryString.stringify(search) });
|
||||
}}
|
||||
destroyOnClose
|
||||
visible={search.billid}
|
||||
open={search.billid}
|
||||
>
|
||||
<BillDetailEditComponent />
|
||||
</Drawer>
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
|
||||
import { Button, Checkbox, Form, Modal, notification, Space } from "antd";
|
||||
import _ from "lodash";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, { useEffect, useState, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_NEW_BILL } from "../../graphql/bills.queries";
|
||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
||||
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
||||
import {
|
||||
QUERY_JOB_LBR_ADJUSTMENTS,
|
||||
UPDATE_JOB,
|
||||
} from "../../graphql/jobs.queries";
|
||||
import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries";
|
||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
||||
@@ -21,15 +20,15 @@ import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import confirmDialog from "../../utils/asyncConfirm";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import BillFormContainer from "../bill-form/bill-form.container";
|
||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
||||
import useLocalStorage from "../../utils/useLocalStorage";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
billEnterModal: selectBillEnterModal,
|
||||
@@ -38,8 +37,8 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
|
||||
insertAuditTrail: ({ jobid, billid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, billid, operation })),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
});
|
||||
|
||||
const Templates = TemplateList("job_special");
|
||||
@@ -64,19 +63,9 @@ function BillEnterModalContainer({
|
||||
"enter_bill_generate_label",
|
||||
false
|
||||
);
|
||||
const { Enhanced_Payroll } = useTreatments(
|
||||
["Enhanced_Payroll"],
|
||||
{},
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
const formValues = useMemo(() => {
|
||||
return {
|
||||
...billEnterModal.context.bill,
|
||||
//Added as a part of IO-2436 for capturing parts price changes.
|
||||
billlines: billEnterModal.context?.bill?.billlines?.map((line) => ({
|
||||
...line,
|
||||
original_actual_price: line.actual_price,
|
||||
})),
|
||||
jobid:
|
||||
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
|
||||
federal_tax_rate:
|
||||
@@ -109,7 +98,6 @@ function BillEnterModalContainer({
|
||||
} = values;
|
||||
|
||||
let adjustmentsToInsert = {};
|
||||
let payrollAdjustmentsToInsert = [];
|
||||
|
||||
const r1 = await insertBill({
|
||||
variables: {
|
||||
@@ -125,72 +113,28 @@ function BillEnterModalContainer({
|
||||
lbr_adjustment,
|
||||
location: lineLocation,
|
||||
part_type,
|
||||
create_ppc,
|
||||
original_actual_price,
|
||||
...restI
|
||||
} = i;
|
||||
|
||||
if (Enhanced_Payroll.treatment === "on") {
|
||||
if (
|
||||
deductedfromlbr &&
|
||||
true //payroll is on
|
||||
) {
|
||||
payrollAdjustmentsToInsert.push({
|
||||
id: i.joblineid,
|
||||
convertedtolbr: true,
|
||||
convertedtolbr_data: {
|
||||
mod_lb_hrs: lbr_adjustment.mod_lb_hrs * -1,
|
||||
mod_lbr_ty: lbr_adjustment.mod_lbr_ty,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (deductedfromlbr) {
|
||||
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
|
||||
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
|
||||
restI.actual_price / lbr_adjustment.rate;
|
||||
}
|
||||
if (deductedfromlbr) {
|
||||
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
|
||||
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
|
||||
restI.actual_price / lbr_adjustment.rate;
|
||||
}
|
||||
|
||||
return {
|
||||
...restI,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
lbr_adjustment,
|
||||
joblineid: i.joblineid === "noline" ? null : i.joblineid,
|
||||
applicable_taxes: {
|
||||
federal:
|
||||
(i.applicable_taxes && i.applicable_taxes.federal) ||
|
||||
false,
|
||||
state:
|
||||
(i.applicable_taxes && i.applicable_taxes.state) ||
|
||||
false,
|
||||
local:
|
||||
(i.applicable_taxes && i.applicable_taxes.local) ||
|
||||
false,
|
||||
},
|
||||
};
|
||||
}),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
|
||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
payrollAdjustmentsToInsert.map((li) => {
|
||||
return updateJobLines({
|
||||
variables: {
|
||||
lineId: li.id,
|
||||
line: {
|
||||
convertedtolbr: li.convertedtolbr,
|
||||
convertedtolbr_data: li.convertedtolbr_data,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const adjKeys = Object.keys(adjustmentsToInsert);
|
||||
if (adjKeys.length > 0) {
|
||||
//Query the adjustments, merge, and update them.
|
||||
@@ -299,14 +243,6 @@ function BillEnterModalContainer({
|
||||
location: li.location || location,
|
||||
status:
|
||||
bodyshop.md_order_statuses.default_received || "Received*",
|
||||
//Added parts price changes.
|
||||
...(li.create_ppc &&
|
||||
li.original_actual_price !== li.actual_price
|
||||
? {
|
||||
act_price_before_ppc: li.original_actual_price,
|
||||
act_price: li.actual_price,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -369,18 +305,16 @@ function BillEnterModalContainer({
|
||||
insertAuditTrail({
|
||||
jobid: values.jobid,
|
||||
billid: billId,
|
||||
operation: AuditTrailMapping.billposted(
|
||||
r1.data.insert_bills.returning[0].invoice_number
|
||||
),
|
||||
operation: AuditTrailMapping.billposted(remainingValues.invoice_number),
|
||||
});
|
||||
|
||||
if (enterAgain) {
|
||||
// form.resetFields();
|
||||
form.resetFields();
|
||||
form.resetFields();
|
||||
form.setFieldsValue({
|
||||
...formValues,
|
||||
billlines: [],
|
||||
});
|
||||
form.resetFields();
|
||||
} else {
|
||||
toggleModalVisible();
|
||||
}
|
||||
@@ -410,7 +344,7 @@ function BillEnterModalContainer({
|
||||
<Modal
|
||||
title={t("bills.labels.new")}
|
||||
width={"98%"}
|
||||
visible={billEnterModal.visible}
|
||||
open={billEnterModal.visible}
|
||||
okText={t("general.actions.save")}
|
||||
keyboard="false"
|
||||
onOk={() => form.submit()}
|
||||
@@ -455,9 +389,6 @@ function BillEnterModalContainer({
|
||||
setEnterAgain(false);
|
||||
}}
|
||||
>
|
||||
<button onClick={() => console.log(form.getFieldsValue("billlines"))}>
|
||||
get billlines
|
||||
</button>
|
||||
<BillFormContainer
|
||||
form={form}
|
||||
disableInvNumber={billEnterModal.context.disableInvNumber}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Icon, { UploadOutlined } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import { MdOpenInNew } from "react-icons/md";
|
||||
import {
|
||||
Alert,
|
||||
Divider,
|
||||
@@ -12,17 +12,14 @@ import {
|
||||
Switch,
|
||||
Upload,
|
||||
} from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MdOpenInNew } from "react-icons/md";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
@@ -31,6 +28,8 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||
import BillFormLines from "./bill-form.lines.component";
|
||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -50,7 +49,6 @@ export function BillFormComponent({
|
||||
job,
|
||||
loadOutstandingReturns,
|
||||
loadInventory,
|
||||
preferredMake,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -60,11 +58,6 @@ export function BillFormComponent({
|
||||
{},
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
const { ClosingPeriod } = useTreatments(
|
||||
["ClosingPeriod"],
|
||||
{},
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const handleVendorSelect = (props, opt) => {
|
||||
setDiscount(opt.discount);
|
||||
@@ -186,7 +179,6 @@ export function BillFormComponent({
|
||||
<VendorSearchSelect
|
||||
disabled={disabled}
|
||||
options={vendorAutoCompleteOptions}
|
||||
preferredMake={preferredMake}
|
||||
onSelect={handleVendorSelect}
|
||||
/>
|
||||
</Form.Item>
|
||||
@@ -267,37 +259,6 @@ export function BillFormComponent({
|
||||
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} />
|
||||
@@ -366,15 +327,13 @@ export function BillFormComponent({
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow>
|
||||
{
|
||||
// <Form.Item
|
||||
// span={3}
|
||||
// label={t("bills.fields.federal_tax_rate")}
|
||||
// name="federal_tax_rate"
|
||||
// >
|
||||
// <CurrencyInput min={0} disabled={disabled} />
|
||||
// </Form.Item>
|
||||
}
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.federal_tax_rate")}
|
||||
name="federal_tax_rate"
|
||||
>
|
||||
<CurrencyInput min={0} disabled={disabled} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.state_tax_rate")}
|
||||
@@ -382,15 +341,13 @@ export function BillFormComponent({
|
||||
>
|
||||
<CurrencyInput min={0} disabled={disabled} />
|
||||
</Form.Item>
|
||||
{
|
||||
// <Form.Item
|
||||
// span={3}
|
||||
// label={t("bills.fields.local_tax_rate")}
|
||||
// name="local_tax_rate"
|
||||
// >
|
||||
// <CurrencyInput min={0} />
|
||||
// </Form.Item>
|
||||
}
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.local_tax_rate")}
|
||||
name="local_tax_rate"
|
||||
>
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate span={15}>
|
||||
{() => {
|
||||
const values = form.getFieldsValue([
|
||||
@@ -416,25 +373,21 @@ export function BillFormComponent({
|
||||
value={totals.subtotal.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
{
|
||||
// <Statistic
|
||||
// title={t("bills.labels.federal_tax")}
|
||||
// value={totals.federalTax.toFormat()}
|
||||
// precision={2}
|
||||
// />
|
||||
}
|
||||
<Statistic
|
||||
title={t("bills.labels.federal_tax")}
|
||||
value={totals.federalTax.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("bills.labels.state_tax")}
|
||||
value={totals.stateTax.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
{
|
||||
// <Statistic
|
||||
// title={t("bills.labels.local_tax")}
|
||||
// value={totals.localTax.toFormat()}
|
||||
// precision={2}
|
||||
// />
|
||||
}
|
||||
<Statistic
|
||||
title={t("bills.labels.local_tax")}
|
||||
value={totals.localTax.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("bills.labels.entered_total")}
|
||||
value={totals.enteredTotal.toFormat()}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
||||
import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries";
|
||||
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
|
||||
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
|
||||
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
|
||||
import BillFormComponent from "./bill-form.component";
|
||||
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
|
||||
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
|
||||
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
|
||||
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -59,7 +59,6 @@ export function BillFormContainer({
|
||||
disableInvNumber={disableInvNumber}
|
||||
loadOutstandingReturns={loadOutstandingReturns}
|
||||
loadInventory={loadInventory}
|
||||
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
||||
/>
|
||||
{!billEdit && (
|
||||
<BillCmdReturnsTableComponent
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Form,
|
||||
Button, Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tooltip,
|
||||
Tooltip
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -47,13 +45,6 @@ export function BillEnterModalLinesComponent({
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const { Enhanced_Payroll } = useTreatments(
|
||||
["Enhanced_Payroll"],
|
||||
{},
|
||||
bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const columns = (remove) => {
|
||||
return [
|
||||
{
|
||||
@@ -102,7 +93,6 @@ export function BillEnterModalLinesComponent({
|
||||
line_desc: opt.line_desc,
|
||||
quantity: opt.part_qty || 1,
|
||||
actual_price: opt.cost,
|
||||
original_actual_price: opt.cost,
|
||||
cost_center: opt.part_type
|
||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
||||
? opt.part_type !== "PAE"
|
||||
@@ -229,43 +219,6 @@ export function BillEnterModalLinesComponent({
|
||||
}}
|
||||
/>
|
||||
),
|
||||
additional: (record, index) => (
|
||||
<Form.Item
|
||||
dependencies={["billlines", record.name, "actual_price"]}
|
||||
noStyle
|
||||
>
|
||||
{() => {
|
||||
const billLine = getFieldValue(["billlines", record.name]);
|
||||
const jobLine = lineData.find(
|
||||
(line) => line.id === billLine?.joblineid
|
||||
);
|
||||
|
||||
if (
|
||||
!billEdit &&
|
||||
billLine &&
|
||||
jobLine &&
|
||||
billLine?.actual_price !== jobLine?.act_price
|
||||
) {
|
||||
return (
|
||||
<Space size="small">
|
||||
<Form.Item
|
||||
noStyle
|
||||
label={t("joblines.fields.create_ppc")}
|
||||
key={`${index}ppc`}
|
||||
valuePropName="checked"
|
||||
name={[record.name, "create_ppc"]}
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
{t("joblines.fields.create_ppc")}
|
||||
</Space>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}}
|
||||
</Form.Item>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.actual_cost"),
|
||||
@@ -407,7 +360,7 @@ export function BillEnterModalLinesComponent({
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
additional: (record, index) => (
|
||||
<Form.Item shouldUpdate noStyle style={{ display: "inline-block" }}>
|
||||
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
|
||||
{() => {
|
||||
const price = getFieldValue([
|
||||
"billlines",
|
||||
@@ -422,31 +375,12 @@ export function BillEnterModalLinesComponent({
|
||||
"rate",
|
||||
]);
|
||||
|
||||
const billline = getFieldValue(["billlines", record.name]);
|
||||
|
||||
const jobline = lineData.find(
|
||||
(line) => line.id === billline?.joblineid
|
||||
);
|
||||
|
||||
const employeeTeamName = bodyshop.employee_teams.find(
|
||||
(team) => team.id === jobline?.assigned_team
|
||||
);
|
||||
|
||||
if (getFieldValue(["billlines", record.name, "deductedfromlbr"]))
|
||||
return (
|
||||
<div>
|
||||
<Space>
|
||||
{t("joblines.fields.assigned_team", {
|
||||
name: employeeTeamName?.name,
|
||||
})}
|
||||
{`${jobline.mod_lb_hrs} units/${t(
|
||||
`joblines.fields.lbr_types.${jobline.mod_lbr_ty}`
|
||||
)}`}
|
||||
</Space>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.mod_lbr_ty")}
|
||||
key={`${index}modlbrty`}
|
||||
initialValue={jobline ? jobline.mod_lbr_ty : null}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
@@ -500,44 +434,22 @@ export function BillEnterModalLinesComponent({
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{Enhanced_Payroll.treatment === "on" ? (
|
||||
<Form.Item
|
||||
label={t("billlines.labels.mod_lbr_adjustment")}
|
||||
name={[record.name, "lbr_adjustment", "mod_lb_hrs"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
precision={5}
|
||||
min={0.01}
|
||||
max={jobline ? jobline.mod_lb_hrs : 0}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item
|
||||
label={t("jobs.labels.adjustmentrate")}
|
||||
name={[record.name, "lbr_adjustment", "rate"]}
|
||||
initialValue={bodyshop.default_adjustment_rate}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={2} min={0.01} />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Space>
|
||||
{price &&
|
||||
adjustmentRate &&
|
||||
`${(price / adjustmentRate).toFixed(1)} hrs`}
|
||||
</Space>
|
||||
<Form.Item
|
||||
label={t("jobs.labels.adjustmentrate")}
|
||||
name={[record.name, "lbr_adjustment", "rate"]}
|
||||
initialValue={bodyshop.default_adjustment_rate}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={2} min={0.01} />
|
||||
</Form.Item>
|
||||
{price &&
|
||||
adjustmentRate &&
|
||||
`${(price / adjustmentRate).toFixed(1)} hrs`}
|
||||
</div>
|
||||
);
|
||||
return <></>;
|
||||
@@ -545,21 +457,21 @@ export function BillEnterModalLinesComponent({
|
||||
</Form.Item>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// title: t("billlines.fields.federal_tax_applicable"),
|
||||
// dataIndex: "applicable_taxes.federal",
|
||||
// editable: true,
|
||||
{
|
||||
title: t("billlines.fields.federal_tax_applicable"),
|
||||
dataIndex: "applicable_taxes.federal",
|
||||
editable: true,
|
||||
|
||||
// formItemProps: (field) => {
|
||||
// return {
|
||||
// key: `${field.index}fedtax`,
|
||||
// valuePropName: "checked",
|
||||
// // initialValue: true,
|
||||
// name: [field.name, "applicable_taxes", "federal"],
|
||||
// };
|
||||
// },
|
||||
// formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
// },
|
||||
formItemProps: (field) => {
|
||||
return {
|
||||
key: `${field.index}fedtax`,
|
||||
valuePropName: "checked",
|
||||
initialValue: true,
|
||||
name: [field.name, "applicable_taxes", "federal"],
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.state_tax_applicable"),
|
||||
dataIndex: "applicable_taxes.state",
|
||||
@@ -574,20 +486,20 @@ export function BillEnterModalLinesComponent({
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
},
|
||||
// {
|
||||
// title: t("billlines.fields.local_tax_applicable"),
|
||||
// dataIndex: "applicable_taxes.local",
|
||||
// editable: true,
|
||||
{
|
||||
title: t("billlines.fields.local_tax_applicable"),
|
||||
dataIndex: "applicable_taxes.local",
|
||||
editable: true,
|
||||
|
||||
// formItemProps: (field) => {
|
||||
// return {
|
||||
// key: `${field.index}localtax`,
|
||||
// valuePropName: "checked",
|
||||
// name: [field.name, "applicable_taxes", "local"],
|
||||
// };
|
||||
// },
|
||||
// formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
// },
|
||||
formItemProps: (field) => {
|
||||
return {
|
||||
key: `${field.index}localtax`,
|
||||
valuePropName: "checked",
|
||||
name: [field.name, "applicable_taxes", "local"],
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
|
||||
@@ -712,7 +624,7 @@ const EditableCell = ({
|
||||
if (additional)
|
||||
return (
|
||||
<td {...restProps}>
|
||||
<div size="small">
|
||||
<Space size="small">
|
||||
<Form.Item
|
||||
name={dataIndex}
|
||||
labelCol={{ span: 0 }}
|
||||
@@ -721,7 +633,7 @@ const EditableCell = ({
|
||||
{(formInput && formInput(record, record.name)) || children}
|
||||
</Form.Item>
|
||||
{additional && additional(record, record.name)}
|
||||
</div>
|
||||
</Space>
|
||||
</td>
|
||||
);
|
||||
if (wrapper)
|
||||
|
||||
@@ -19,14 +19,14 @@ export const CalculateBillTotal = (invoice) => {
|
||||
}).multiply(i.quantity || 1);
|
||||
|
||||
subtotal = subtotal.add(itemTotal);
|
||||
if (i.applicable_taxes?.federal) {
|
||||
if (i.applicable_taxes.federal) {
|
||||
federalTax = federalTax.add(
|
||||
itemTotal.percentage(federal_tax_rate || 0)
|
||||
);
|
||||
}
|
||||
if (i.applicable_taxes?.state)
|
||||
if (i.applicable_taxes.state)
|
||||
stateTax = stateTax.add(itemTotal.percentage(state_tax_rate || 0));
|
||||
if (i.applicable_taxes?.local)
|
||||
if (i.applicable_taxes.local)
|
||||
localTax = localTax.add(itemTotal.percentage(local_tax_rate || 0));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -63,12 +63,6 @@ const BillLineSearchSelect = (
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
|
||||
</span>
|
||||
{item.act_price === 0 && item.mod_lb_hrs > 0 && (
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{`${item.mod_lb_hrs} units`}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{item.act_price
|
||||
? `$${item.act_price && item.act_price.toFixed(2)}`
|
||||
|
||||
@@ -7,9 +7,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import GlobalSearch from "../global-search/global-search.component";
|
||||
import GlobalSearchOs from "../global-search/global-search-os.component";
|
||||
import "./breadcrumbs.styles.scss";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
breadcrumbs: selectBreadcrumbs,
|
||||
@@ -17,36 +15,45 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
|
||||
const { OpenSearch } = useTreatments(
|
||||
["OpenSearch"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
return (
|
||||
<Row className="breadcrumb-container">
|
||||
<Col xs={24} sm={24} md={16}>
|
||||
<Breadcrumb separator=">">
|
||||
<Breadcrumb.Item>
|
||||
<Link to={`/manage`}>
|
||||
<HomeFilled />{" "}
|
||||
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
|
||||
""}
|
||||
</Link>
|
||||
</Breadcrumb.Item>
|
||||
{breadcrumbs.map((item) =>
|
||||
item.link ? (
|
||||
<Breadcrumb.Item key={item.label}>
|
||||
<Link to={item.link}>{item.label} </Link>
|
||||
</Breadcrumb.Item>
|
||||
) : (
|
||||
<Breadcrumb.Item key={item.label}>{item.label}</Breadcrumb.Item>
|
||||
)
|
||||
)}
|
||||
</Breadcrumb>
|
||||
<Breadcrumb
|
||||
separator=">"
|
||||
items={[
|
||||
{
|
||||
title: (
|
||||
<Link to={`/manage`}>
|
||||
<HomeFilled />{" "}
|
||||
{(bodyshop &&
|
||||
bodyshop.shopname &&
|
||||
`(${bodyshop.shopname})`) ||
|
||||
""}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
...breadcrumbs.map((item) =>
|
||||
item.link
|
||||
? {
|
||||
title: (
|
||||
<Link key={item.label} to={item.link}>
|
||||
{item.label}{" "}
|
||||
</Link>
|
||||
),
|
||||
}
|
||||
: {
|
||||
title: (
|
||||
<React.Fragment key={item.label}>
|
||||
{item.label}
|
||||
</React.Fragment>
|
||||
),
|
||||
}
|
||||
),
|
||||
]}
|
||||
></Breadcrumb>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={8}>
|
||||
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />}
|
||||
<GlobalSearch />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
@@ -70,7 +70,7 @@ export function ContractsFindModalContainer({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
open={visible}
|
||||
width="70%"
|
||||
title={t("payments.labels.findermodal")}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
|
||||
@@ -10,10 +10,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("job_ca_bc_pvrt_calculate");
|
||||
form.setFieldsValue({
|
||||
ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2),
|
||||
});
|
||||
form.setFields([{ name: "ca_bc_pvrt", touched: true }]);
|
||||
form.setFieldsValue({ ca_bc_pvrt: ((values.rate||0) * (values.days||0)).toFixed(2) });
|
||||
setVisibility(false);
|
||||
};
|
||||
|
||||
@@ -38,7 +35,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
||||
<Popover
|
||||
destroyTooltipOnHide
|
||||
content={popContent}
|
||||
visible={visibility}
|
||||
open={visibility}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Button disabled={disabled} onClick={() => setVisibility(true)}>
|
||||
|
||||
@@ -1,374 +0,0 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Form,
|
||||
Input,
|
||||
Row,
|
||||
Space,
|
||||
Spin,
|
||||
Statistic,
|
||||
notification,
|
||||
} from "antd";
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
INSERT_PAYMENT_RESPONSE,
|
||||
QUERY_RO_AND_OWNER_BY_JOB_PKS,
|
||||
} from "../../graphql/payment_response.queries";
|
||||
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
cardPaymentModal: selectCardPayment,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
|
||||
});
|
||||
|
||||
const CardPaymentModalComponent = ({
|
||||
bodyshop,
|
||||
cardPaymentModal,
|
||||
toggleModalVisible,
|
||||
insertAuditTrail,
|
||||
}) => {
|
||||
const { context } = cardPaymentModal;
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [, { data, refetch, queryLoading }] = useLazyQuery(
|
||||
QUERY_RO_AND_OWNER_BY_JOB_PKS,
|
||||
{
|
||||
variables: { jobids: [context.jobid] },
|
||||
skip: true,
|
||||
}
|
||||
);
|
||||
|
||||
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
|
||||
//Initialize the intellipay window.
|
||||
const SetIntellipayCallbackFunctions = () => {
|
||||
console.log("*** Set IntelliPay callback functions.");
|
||||
window.intellipay.runOnClose(() => {
|
||||
//window.intellipay.initialize();
|
||||
});
|
||||
|
||||
window.intellipay.runOnApproval(async function (response) {
|
||||
console.warn("*** Running On Approval Script ***");
|
||||
form.setFieldValue("paymentResponse", response);
|
||||
form.submit();
|
||||
});
|
||||
|
||||
window.intellipay.runOnNonApproval(async function (response) {
|
||||
// Mutate unsuccessful payment
|
||||
|
||||
const { payments } = form.getFieldsValue();
|
||||
|
||||
await insertPaymentResponse({
|
||||
variables: {
|
||||
paymentResponse: payments.map((payment) => ({
|
||||
amount: payment.amount,
|
||||
bodyshopid: bodyshop.id,
|
||||
jobid: payment.jobid,
|
||||
declinereason: response.declinereason,
|
||||
ext_paymentid: response.paymentid.toString(),
|
||||
successful: false,
|
||||
response,
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
payments.forEach((payment) =>
|
||||
insertAuditTrail({
|
||||
jobid: payment.jobid,
|
||||
operation: AuditTrailMapping.failedpayment(),
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
try {
|
||||
await insertPayment({
|
||||
variables: {
|
||||
paymentInput: values.payments.map((payment) => ({
|
||||
amount: payment.amount,
|
||||
transactionid: (values.paymentResponse.paymentid || "").toString(),
|
||||
payer: t("payments.labels.customer"),
|
||||
type: values.paymentResponse.cardbrand,
|
||||
jobid: payment.jobid,
|
||||
date: moment(Date.now()),
|
||||
payment_responses: {
|
||||
data: [
|
||||
{
|
||||
amount: payment.amount,
|
||||
bodyshopid: bodyshop.id,
|
||||
|
||||
jobid: payment.jobid,
|
||||
declinereason: values.paymentResponse.declinereason,
|
||||
ext_paymentid: values.paymentResponse.paymentid.toString(),
|
||||
successful: true,
|
||||
response: values.paymentResponse,
|
||||
},
|
||||
],
|
||||
},
|
||||
})),
|
||||
},
|
||||
refetchQueries: ["GET_JOB_BY_PK"],
|
||||
});
|
||||
toggleModalVisible();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("payments.errors.inserting", { error: error.message }),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIntelliPayCharge = async () => {
|
||||
setLoading(true);
|
||||
|
||||
//Validate
|
||||
try {
|
||||
await form.validateFields();
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||
bodyshop,
|
||||
refresh: !!window.intellipay,
|
||||
});
|
||||
|
||||
if (window.intellipay) {
|
||||
// eslint-disable-next-line no-eval
|
||||
eval(response.data);
|
||||
SetIntellipayCallbackFunctions();
|
||||
window.intellipay.autoOpen();
|
||||
} else {
|
||||
var rg = document.createRange();
|
||||
let node = rg.createContextualFragment(response.data);
|
||||
document.documentElement.appendChild(node);
|
||||
SetIntellipayCallbackFunctions();
|
||||
window.intellipay.isAutoOpen = true;
|
||||
window.intellipay.initialize();
|
||||
}
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("job_payments.notifications.error.openingip"),
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title="Card Payment">
|
||||
<Spin spinning={loading}>
|
||||
<Form
|
||||
onFinish={handleFinish}
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
payments: context.jobid ? [{ jobid: context.jobid }] : [],
|
||||
}}
|
||||
>
|
||||
<Form.List name={["payments"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={16}>
|
||||
<Form.Item
|
||||
key={`${index}jobid`}
|
||||
label={t("jobs.fields.ro_number")}
|
||||
name={[field.name, "jobid"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<JobSearchSelectComponent
|
||||
notExported={false}
|
||||
clm_no
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Item
|
||||
key={`${index}amount`}
|
||||
label={t("payments.fields.amount")}
|
||||
name={[field.name, "amount"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyFormItemComponent />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={2}>
|
||||
<DeleteFilled
|
||||
style={{ margin: "1rem" }}
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("general.actions.add")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
|
||||
<Form.Item
|
||||
shouldUpdate={(prevValues, curValues) =>
|
||||
prevValues.payments?.map((p) => p?.jobid).join() !==
|
||||
curValues.payments?.map((p) => p?.jobid).join()
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
console.log("Updating the owner info section.");
|
||||
//If all of the job ids have been fileld in, then query and update the IP field.
|
||||
const { payments } = form.getFieldsValue();
|
||||
if (
|
||||
payments?.length > 0 &&
|
||||
payments?.filter((p) => p?.jobid).length === payments?.length
|
||||
) {
|
||||
console.log("**Calling refetch.");
|
||||
refetch({ jobids: payments.map((p) => p.jobid) });
|
||||
}
|
||||
console.log(
|
||||
"Acc info",
|
||||
data,
|
||||
payments && data && data.jobs.length > 0
|
||||
? data.jobs.map((j) => j.ro_number).join(", ")
|
||||
: null
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
className="ipayfield"
|
||||
data-ipayname="account"
|
||||
//type="hidden"
|
||||
value={
|
||||
payments && data && data.jobs.length > 0
|
||||
? data.jobs.map((j) => j.ro_number).join(", ")
|
||||
: null
|
||||
}
|
||||
hidden
|
||||
/>
|
||||
<Input
|
||||
className="ipayfield"
|
||||
data-ipayname="email"
|
||||
// type="hidden"
|
||||
value={
|
||||
payments && data && data.jobs.length > 0
|
||||
? data.jobs.filter((j) => j.ownr_ea)[0]?.ownr_ea
|
||||
: null
|
||||
}
|
||||
hidden
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
shouldUpdate={(prevValues, curValues) =>
|
||||
prevValues.payments?.map((p) => p?.amount).join() !==
|
||||
curValues.payments?.map((p) => p?.amount).join()
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
const { payments } = form.getFieldsValue();
|
||||
const totalAmountToCharge = payments?.reduce((acc, val) => {
|
||||
return acc + (val?.amount || 0);
|
||||
}, 0);
|
||||
|
||||
return (
|
||||
<Space style={{ float: "right" }}>
|
||||
<Statistic
|
||||
title="Amount To Charge"
|
||||
value={totalAmountToCharge}
|
||||
precision={2}
|
||||
/>
|
||||
<Input
|
||||
className="ipayfield"
|
||||
data-ipayname="amount"
|
||||
//type="hidden"
|
||||
value={totalAmountToCharge?.toFixed(2)}
|
||||
hidden
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
// data-ipayname="submit"
|
||||
className="ipayfield"
|
||||
loading={queryLoading || loading}
|
||||
disabled={!(totalAmountToCharge > 0)}
|
||||
onClick={handleIntelliPayCharge}
|
||||
>
|
||||
{t("job_payments.buttons.proceedtopayment")}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* Lightbox payment response when it is completed */}
|
||||
<Form.Item name="paymentResponse" hidden>
|
||||
<Input type="hidden" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Spin>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(CardPaymentModalComponent);
|
||||
@@ -1,57 +0,0 @@
|
||||
import { Button, Modal } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CardPaymentModalComponent from "./card-payment-modal.component.";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
cardPaymentModal: selectCardPayment,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
|
||||
});
|
||||
|
||||
function CardPaymentModalContainer({
|
||||
cardPaymentModal,
|
||||
toggleModalVisible,
|
||||
bodyshop,
|
||||
}) {
|
||||
const { visible } = cardPaymentModal;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCancel = () => {
|
||||
toggleModalVisible();
|
||||
};
|
||||
|
||||
const handleOK = () => {
|
||||
toggleModalVisible();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={visible}
|
||||
onOk={handleOK}
|
||||
onCancel={handleCancel}
|
||||
footer={[
|
||||
<Button key="back" onClick={handleCancel}>
|
||||
{t("job_payments.buttons.goback")}
|
||||
</Button>,
|
||||
]}
|
||||
width="80%"
|
||||
destroyOnClose
|
||||
>
|
||||
<CardPaymentModalComponent />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(CardPaymentModalContainer);
|
||||
@@ -1,19 +1,12 @@
|
||||
import { Badge, List, Tag } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
AutoSizer,
|
||||
CellMeasurer,
|
||||
CellMeasurerCache,
|
||||
List as VirtualizedList,
|
||||
} from "react-virtualized";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
|
||||
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||
import { TimeAgoFormatter } from "../../utils/DateFormatter";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
import "./chat-conversation-list.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -25,95 +18,59 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(setSelectedConversation(conversationId)),
|
||||
});
|
||||
|
||||
function ChatConversationListComponent({
|
||||
export function ChatConversationListComponent({
|
||||
conversationList,
|
||||
selectedConversation,
|
||||
setSelectedConversation,
|
||||
loadMoreConversations,
|
||||
}) {
|
||||
const cache = new CellMeasurerCache({
|
||||
fixedWidth: true,
|
||||
defaultHeight: 60,
|
||||
});
|
||||
|
||||
const rowRenderer = ({ index, key, style, parent }) => {
|
||||
const item = conversationList[index];
|
||||
|
||||
return (
|
||||
<CellMeasurer
|
||||
key={key}
|
||||
cache={cache}
|
||||
parent={parent}
|
||||
columnIndex={0}
|
||||
rowIndex={index}
|
||||
>
|
||||
<List.Item
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
className={`chat-list-item ${
|
||||
item.id === selectedConversation
|
||||
? "chat-list-selected-conversation"
|
||||
: null
|
||||
}`}
|
||||
style={style}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
{item.label && <div className="chat-name">{item.label}</div>}
|
||||
{item.job_conversations.length > 0 ? (
|
||||
<div className="chat-name">
|
||||
{item.job_conversations.map((j, idx) => (
|
||||
<div key={idx}>
|
||||
<OwnerNameDisplay ownerObject={j.job} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ display: "inline-block" }}>
|
||||
<div>
|
||||
{item.job_conversations.length > 0
|
||||
? item.job_conversations.map((j, idx) => (
|
||||
<Tag key={idx} className="ro-number-tag">
|
||||
{j.job.ro_number}
|
||||
</Tag>
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
|
||||
</div>
|
||||
<Badge count={item.messages_aggregate.aggregate.count || 0} />
|
||||
</List.Item>
|
||||
</CellMeasurer>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="chat-list-container">
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<VirtualizedList
|
||||
height={height}
|
||||
width={width}
|
||||
rowCount={conversationList.length}
|
||||
rowHeight={cache.rowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
|
||||
if (scrollTop + clientHeight === scrollHeight) {
|
||||
loadMoreConversations();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<List
|
||||
bordered
|
||||
dataSource={conversationList}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
key={item.id}
|
||||
onClick={() => setSelectedConversation(item.id)}
|
||||
className={`chat-list-item ${
|
||||
item.id === selectedConversation
|
||||
? "chat-list-selected-conversation"
|
||||
: null
|
||||
}`}
|
||||
>
|
||||
<div sryle={{ 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 sryle={{ 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>
|
||||
)}
|
||||
</AutoSizer>
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
}
|
||||
.chat-list-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
border: 1px solid gainsboro;
|
||||
}
|
||||
|
||||
.chat-list-item {
|
||||
@@ -22,6 +21,4 @@
|
||||
.ro-number-tag {
|
||||
align-self: baseline;
|
||||
}
|
||||
padding: 12px 24px;
|
||||
border-bottom: 1px solid gainsboro;
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ export function ChatMediaSelector({
|
||||
}
|
||||
title={t("messaging.labels.selectmedia")}
|
||||
trigger="click"
|
||||
visible={visible}
|
||||
onVisibleChange={handleVisibleChange}
|
||||
open={visible}
|
||||
onOpenChange={handleVisibleChange}
|
||||
>
|
||||
<Badge count={selectedMedia.filter((s) => s.isSelected).length}>
|
||||
<PictureFilled style={{ margin: "0 .5rem" }} />
|
||||
|
||||
@@ -8,23 +8,15 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
searchingForConversation: searchingForConversation,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
});
|
||||
|
||||
export function ChatOpenButton({
|
||||
bodyshop,
|
||||
searchingForConversation,
|
||||
phone,
|
||||
jobid,
|
||||
openChatByPhone,
|
||||
}) {
|
||||
export function ChatOpenButton({ bodyshop, phone, jobid, openChatByPhone }) {
|
||||
const { t } = useTranslation();
|
||||
if (!phone) return <></>;
|
||||
|
||||
@@ -37,7 +29,7 @@ export function ChatOpenButton({
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const p = parsePhoneNumber(phone, "CA");
|
||||
if (searchingForConversation) return; //This is to prevent finding the same thing twice.
|
||||
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid });
|
||||
} else {
|
||||
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
ShrinkOutlined,
|
||||
SyncOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useLazyQuery, useQuery } from "@apollo/client";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -47,13 +47,12 @@ export function ChatPopupComponent({
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
|
||||
const [getConversations, { loading, data, refetch, fetchMore }] =
|
||||
useLazyQuery(CONVERSATION_LIST_QUERY, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !chatVisible,
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
skip: !chatVisible,
|
||||
...(pollInterval > 0 ? { pollInterval } : {}),
|
||||
});
|
||||
|
||||
const fcmToken = sessionStorage.getItem("fcmtoken");
|
||||
|
||||
@@ -66,22 +65,15 @@ export function ChatPopupComponent({
|
||||
}, [fcmToken]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatVisible)
|
||||
getConversations({
|
||||
variables: {
|
||||
offset: 0,
|
||||
},
|
||||
});
|
||||
}, [chatVisible, getConversations]);
|
||||
if (called && chatVisible) refetch();
|
||||
}, [chatVisible, called, refetch]);
|
||||
|
||||
const loadMoreConversations = useCallback(() => {
|
||||
if (data)
|
||||
fetchMore({
|
||||
variables: {
|
||||
offset: data.conversations.length,
|
||||
},
|
||||
});
|
||||
}, [data, fetchMore]);
|
||||
// const unreadCount = data
|
||||
// ? data.conversations.reduce(
|
||||
// (acc, val) => val.messages_aggregate.aggregate.count + acc,
|
||||
// 0
|
||||
// )
|
||||
// : 0;
|
||||
|
||||
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
|
||||
|
||||
@@ -118,7 +110,6 @@ export function ChatPopupComponent({
|
||||
) : (
|
||||
<ChatConversationListComponent
|
||||
conversationList={data ? data.conversations : []}
|
||||
loadMoreConversations={loadMoreConversations}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
@@ -132,7 +123,7 @@ export function ChatPopupComponent({
|
||||
onClick={() => toggleChatVisible()}
|
||||
style={{ cursor: "pointer" }}
|
||||
>
|
||||
<MessageOutlined className="chat-popup-info-icon" />
|
||||
<MessageOutlined />
|
||||
<strong>{t("messaging.labels.messaging")}</strong>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -13,9 +13,6 @@
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.chat-popup-info-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 992px) {
|
||||
.chat-popup {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PlusCircleOutlined } from "@ant-design/icons";
|
||||
import { Dropdown, Menu } from "antd";
|
||||
import { Dropdown } from "antd";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -16,19 +16,15 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
|
||||
export function ChatPresetsComponent({ bodyshop, setMessage, className }) {
|
||||
const menu = (
|
||||
<Menu>
|
||||
{bodyshop.md_messaging_presets.map((i, idx) => (
|
||||
<Menu.Item onClick={() => setMessage(i.text)} key={idx}>
|
||||
{i.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
const menu = bodyshop.md_messaging_presets.map((i, idx) => ({
|
||||
label: i.label,
|
||||
key: idx,
|
||||
onClick: () => setMessage(i.text),
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Dropdown trigger={["click"]} overlay={menu}>
|
||||
<Dropdown trigger={["click"]} menu={{ items: menu }} placement="top">
|
||||
<PlusCircleOutlined />
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
@@ -59,14 +59,6 @@ export default function ContractsCarsComponent({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "model" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("courtesycars.fields.color"),
|
||||
dataIndex: "color",
|
||||
key: "color",
|
||||
sorter: (a, b) => alphaSort(a.color, b.color),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "color" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("courtesycars.fields.plate"),
|
||||
dataIndex: "plate",
|
||||
@@ -101,9 +93,6 @@ export default function ContractsCarsComponent({
|
||||
(cc.model || "")
|
||||
.toLowerCase()
|
||||
.includes(state.search.toLowerCase()) ||
|
||||
(cc.color || "")
|
||||
.toLowerCase()
|
||||
.includes(state.search.toLowerCase()) ||
|
||||
(cc.plate || "").toLowerCase().includes(state.search.toLowerCase())
|
||||
);
|
||||
|
||||
|
||||
@@ -390,7 +390,7 @@ export function ContractConvertToRo({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Popover content={popContent} visible={visible}>
|
||||
<Popover content={popContent} open={visible}>
|
||||
<Button
|
||||
onClick={() => setVisible(true)}
|
||||
loading={loading}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function ContractsJobsComponent({
|
||||
loading,
|
||||
@@ -176,7 +175,7 @@ export default function ContractsJobsComponent({
|
||||
loading={loading}
|
||||
pagination={{
|
||||
position: "top",
|
||||
defaultPageSize: pageLimit,
|
||||
defaultPageSize: 10,
|
||||
defaultCurrent: defaultCurrent,
|
||||
}}
|
||||
columns={columns}
|
||||
|
||||
@@ -2,10 +2,10 @@ import { useQuery } from "@apollo/client";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import ContractJobsComponent from "./contract-jobs.component";
|
||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -15,7 +15,6 @@ export function ContractJobsContainer({ selectedJobState, bodyshop }) {
|
||||
const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
||||
variables: {
|
||||
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"],
|
||||
isConverted: true,
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function ContractLicenseDecodeButton({ form }) {
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
visible={modalVisible}
|
||||
open={modalVisible}
|
||||
okText={t("contracts.actions.senddltoform")}
|
||||
onOk={handleInsertForm}
|
||||
okButtonProps={{ disabled: !!!decodedBarcode }}
|
||||
|
||||
@@ -59,7 +59,7 @@ export function ContractsFindModalContainer({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
open={visible}
|
||||
width="70%"
|
||||
title={t("contracts.labels.findermodal")}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
|
||||
@@ -4,16 +4,15 @@ import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
|
||||
import moment from "moment";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import moment from "moment";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -210,7 +209,7 @@ export function ContractsList({
|
||||
}}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: pageLimit,
|
||||
pageSize: 25,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { Dropdown, Menu } from "antd";
|
||||
import { Dropdown } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -13,25 +13,14 @@ const mapStateToProps = createStructuredSelector({
|
||||
export function ContractsRatesChangeButton({ disabled, form, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClick = ({ item, key, keyPath }) => {
|
||||
const { label, ...rate } = item.props.value;
|
||||
form.setFieldsValue(rate);
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.md_ccc_rates.map((rate, idx) => (
|
||||
<Menu.Item value={rate} key={idx}>
|
||||
{rate.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
const menu = bodyshop.md_ccc_rates.map((rate, idx) => ({
|
||||
onClick: () => form.setFieldsValue(rate),
|
||||
key: idx,
|
||||
label: rate.label,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu} disabled={disabled}>
|
||||
<Dropdown menu={{ items: menu }} disabled={disabled}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function CourtesyCarContractListComponent({
|
||||
contracts,
|
||||
@@ -90,7 +89,7 @@ export default function CourtesyCarContractListComponent({
|
||||
scroll={{ x: true }}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: pageLimit,
|
||||
pageSize: 25,
|
||||
current: parseInt(page || 1),
|
||||
total: totalContracts,
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { WarningFilled } from "@ant-design/icons";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, Form, Input, InputNumber, PageHeader, Space } from "antd";
|
||||
import { PageHeader } from '@ant-design/pro-layout';
|
||||
import { Button, Form, Input, InputNumber, Space } from "antd";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -34,18 +35,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
|
||||
{/* <FormFieldsChanged form={form} /> */}
|
||||
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.year")}
|
||||
name="year"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.make")}
|
||||
name="make"
|
||||
@@ -70,6 +59,18 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.year")}
|
||||
name="year"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.plate")}
|
||||
name="plate"
|
||||
@@ -118,7 +119,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={0} />
|
||||
<InputNumber precision={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.fleetnumber")}
|
||||
@@ -213,23 +214,49 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
>
|
||||
<CourtesyCarStatus />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.nextservicekm")}
|
||||
name="nextservicekm"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.nextservicekm")}
|
||||
name="nextservicekm"
|
||||
label={t("courtesycars.fields.nextservicedate")}
|
||||
name="nextservicedate"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={0} />
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
shouldUpdate={(p, c) =>
|
||||
p.mileage !== c.mileage || p.nextservicekm !== c.nextservicekm
|
||||
p.mileage !== c.mileage ||
|
||||
p.nextservicedate !== c.nextservicedate ||
|
||||
p.nextservicekm !== c.nextservicekm
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
const nextservicedate = form.getFieldValue("nextservicedate");
|
||||
const nextservicekm = form.getFieldValue("nextservicekm");
|
||||
|
||||
const mileageOver =
|
||||
nextservicekm && nextservicekm <= form.getFieldValue("mileage");
|
||||
if (mileageOver)
|
||||
nextservicekm <= form.getFieldValue("mileage");
|
||||
|
||||
const dueForService =
|
||||
nextservicedate && moment(nextservicedate).isBefore(moment());
|
||||
|
||||
if (mileageOver || dueForService)
|
||||
return (
|
||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||
<span>
|
||||
@@ -237,36 +264,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
{t("contracts.labels.cardueforservice")}
|
||||
</span>
|
||||
<span>{`${nextservicekm} km`}</span>
|
||||
</Space>
|
||||
);
|
||||
|
||||
return <></>;
|
||||
}}
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div>
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.nextservicedate")}
|
||||
name="nextservicedate"
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}
|
||||
>
|
||||
{() => {
|
||||
const nextservicedate = form.getFieldValue("nextservicedate");
|
||||
const dueForService =
|
||||
nextservicedate &&
|
||||
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
|
||||
|
||||
if (dueForService)
|
||||
return (
|
||||
<Space direction="vertical" style={{ color: "tomato" }}>
|
||||
<span>
|
||||
<WarningFilled style={{ marginRight: ".3rem" }} />
|
||||
{t("contracts.labels.cardueforservice")}
|
||||
</span>
|
||||
<span>
|
||||
<DateFormatter>{nextservicedate}</DateFormatter>
|
||||
</span>
|
||||
@@ -287,6 +284,12 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
<Form.Item
|
||||
label={t("courtesycars.fields.registrationexpires")}
|
||||
name="registrationexpires"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
@@ -298,8 +301,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
{() => {
|
||||
const expires = form.getFieldValue("registrationexpires");
|
||||
|
||||
const dateover =
|
||||
expires && moment(expires).endOf("day").isBefore(moment());
|
||||
const dateover = expires && moment(expires).isBefore(moment());
|
||||
|
||||
if (dateover)
|
||||
return (
|
||||
@@ -329,13 +331,14 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}
|
||||
shouldUpdate={(p, c) =>
|
||||
p.insuranceexpires !== c.insuranceexpires
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
const expires = form.getFieldValue("insuranceexpires");
|
||||
|
||||
const dateover =
|
||||
expires && moment(expires).endOf("day").isBefore(moment());
|
||||
const dateover = expires && moment(expires).isBefore(moment());
|
||||
|
||||
if (dateover)
|
||||
return (
|
||||
|
||||
@@ -64,7 +64,7 @@ export function CCReturnModalContainer({
|
||||
return (
|
||||
<Modal
|
||||
title={t("courtesycars.labels.return")}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onCancel={() => toggleModalVisible()}
|
||||
width={"90%"}
|
||||
okText={t("general.actions.save")}
|
||||
|
||||
@@ -3,11 +3,9 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Dropdown,
|
||||
Input,
|
||||
Menu,
|
||||
Space,
|
||||
Input, Space,
|
||||
Table,
|
||||
Tooltip,
|
||||
Tooltip
|
||||
} from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
@@ -15,8 +13,8 @@ import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
const [state, setState] = useState({
|
||||
@@ -115,14 +113,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "model" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("courtesycars.fields.color"),
|
||||
dataIndex: "color",
|
||||
key: "color",
|
||||
sorter: (a, b) => alphaSort(a.color, b.color),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "color" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("courtesycars.fields.plate"),
|
||||
dataIndex: "plate",
|
||||
@@ -174,7 +164,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
(c.year || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(c.make || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(c.model || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(c.plate || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
||||
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
|
||||
)
|
||||
: courtesycars;
|
||||
@@ -188,10 +177,14 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
</Button>
|
||||
<Dropdown
|
||||
trigger="click"
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() =>
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
label: t(
|
||||
"printcenter.courtesycarcontract.courtesy_car_inventory"
|
||||
),
|
||||
key: "cc_inv",
|
||||
onClick: () =>
|
||||
GenerateDocument(
|
||||
{
|
||||
name: TemplateList("courtesycar").courtesy_car_inventory
|
||||
@@ -202,13 +195,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
)
|
||||
}
|
||||
>
|
||||
{t("printcenter.courtesycarcontract.courtesy_car_inventory")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<Button>{t("general.labels.print")}</Button>
|
||||
</Dropdown>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
export default function CsiResponseListPaginated({
|
||||
refetch,
|
||||
@@ -107,7 +106,7 @@ export default function CsiResponseListPaginated({
|
||||
loading={loading}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: pageLimit,
|
||||
pageSize: 25,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
}}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { alphaSort } from "../../../utils/sorters";
|
||||
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||
import Dinero from "dinero.js";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import {pageLimit} from "../../../utils/config";
|
||||
|
||||
export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -119,7 +118,7 @@ export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 4em)" }}
|
||||
rowKey="id"
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Space, Table, Tooltip } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import {pageLimit} from "../../../utils/config";
|
||||
|
||||
export default function DashboardScheduledInToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
});
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_in_today)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
const appt = []; // Flatten Data
|
||||
data.scheduled_in_today.forEach((item) => {
|
||||
if (item.job) {
|
||||
var i = {
|
||||
canceled: item.canceled,
|
||||
id: item.id,
|
||||
alt_transport: item.job.alt_transport,
|
||||
clm_no: item.job.clm_no,
|
||||
jobid: item.job.jobid,
|
||||
ins_co_nm: item.job.ins_co_nm,
|
||||
iouparent: item.job.iouparent,
|
||||
ownerid: item.job.ownerid,
|
||||
ownr_co_nm: item.job.ownr_co_nm,
|
||||
ownr_ea: item.job.ownr_ea,
|
||||
ownr_fn: item.job.ownr_fn,
|
||||
ownr_ln: item.job.ownr_ln,
|
||||
ownr_ph1: item.job.ownr_ph1,
|
||||
ownr_ph2: item.job.ownr_ph2,
|
||||
production_vars: item.job.production_vars,
|
||||
ro_number: item.job.ro_number,
|
||||
suspended: item.job.suspended,
|
||||
v_make_desc: item.job.v_make_desc,
|
||||
v_model_desc: item.job.v_model_desc,
|
||||
v_model_yr: item.job.v_model_yr,
|
||||
v_vin: item.job.v_vin,
|
||||
vehicleid: item.job.vehicleid,
|
||||
note: item.note,
|
||||
start: moment(item.start).format("hh:mm a"),
|
||||
title: item.title,
|
||||
};
|
||||
appt.push(i);
|
||||
}
|
||||
});
|
||||
appt.sort(function (a, b) {
|
||||
return new moment(a.start) - new moment(b.start);
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ea"),
|
||||
dataIndex: "ownr_ea",
|
||||
key: "ownr_ea",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.time"),
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (sorter) => {
|
||||
setState({ ...state, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.scheduledintoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
})}
|
||||
{...cardProps}
|
||||
>
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
style={{ height: "85%" }}
|
||||
dataSource={appt}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardScheduledInTodayGql = `
|
||||
scheduled_in_today: appointments(where: {start: {_gte: "${moment()
|
||||
.startOf("day")
|
||||
.toISOString()}", _lte: "${moment()
|
||||
.endOf("day")
|
||||
.toISOString()}"}, canceled: {_eq: false}, block: {_neq: true}}) {
|
||||
canceled
|
||||
id
|
||||
job {
|
||||
alt_transport
|
||||
clm_no
|
||||
jobid: id
|
||||
ins_co_nm
|
||||
iouparent
|
||||
ownerid
|
||||
ownr_co_nm
|
||||
ownr_ea
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
production_vars
|
||||
ro_number
|
||||
suspended
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
vehicleid
|
||||
}
|
||||
note
|
||||
start
|
||||
title
|
||||
}
|
||||
`;
|
||||
@@ -1,211 +0,0 @@
|
||||
import {
|
||||
BranchesOutlined,
|
||||
ExclamationCircleFilled,
|
||||
PauseCircleOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Space, Table, Tooltip } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import {pageLimit} from "../../../utils/config";
|
||||
|
||||
export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
});
|
||||
if (!data) return null;
|
||||
if (!data.scheduled_out_today)
|
||||
return <DashboardRefreshRequired {...cardProps} />;
|
||||
|
||||
data.scheduled_out_today.forEach((item) => {
|
||||
item.scheduled_completion= moment(item.scheduled_completion).format("hh:mm a")
|
||||
});
|
||||
data.scheduled_out_today.sort(function (a, b) {
|
||||
return new Date(a.scheduled_completion) - new Date(b.scheduled_completion);
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
key: "ro_number",
|
||||
render: (text, record) => (
|
||||
<Link
|
||||
to={"/manage/jobs/" + record.jobid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Space>
|
||||
{record.ro_number || t("general.labels.na")}
|
||||
{record.production_vars && record.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{record.suspended && (
|
||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
||||
)}
|
||||
{record.iouparent && (
|
||||
<Tooltip title={t("jobs.labels.iou")}>
|
||||
<BranchesOutlined style={{ color: "orangered" }} />
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.owner"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => {
|
||||
return record.ownerid ? (
|
||||
<Link
|
||||
to={"/manage/owners/" + record.ownerid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={record} />
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph1"),
|
||||
dataIndex: "ownr_ph1",
|
||||
key: "ownr_ph1",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ea"),
|
||||
dataIndex: "ownr_ea",
|
||||
key: "ownr_ea",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
key: "vehicle",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.vehicleid ? (
|
||||
<Link
|
||||
to={"/manage/vehicles/" + record.vehicleid}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`}</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ins_co_nm"),
|
||||
dataIndex: "ins_co_nm",
|
||||
key: "ins_co_nm",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.scheduled_completion"),
|
||||
dataIndex: "scheduled_completion",
|
||||
key: "scheduled_completion",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
{
|
||||
title: t("appointments.fields.alt_transport"),
|
||||
dataIndex: "alt_transport",
|
||||
key: "alt_transport",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (sorter) => {
|
||||
setState({ ...state, sortedInfo: sorter });
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("dashboard.titles.scheduledouttoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
})}
|
||||
{...cardProps}
|
||||
>
|
||||
<div style={{ height: "100%" }}>
|
||||
<Table
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
columns={columns}
|
||||
scroll={{ x: true, y: "calc(100% - 2em)" }}
|
||||
rowKey="id"
|
||||
style={{ height: "85%" }}
|
||||
dataSource={data.scheduled_out_today}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export const DashboardScheduledOutTodayGql = `
|
||||
scheduled_out_today: jobs(where: {
|
||||
date_invoiced: {_is_null: true},
|
||||
ro_number: {_is_null: false},
|
||||
voided: {_eq: false},
|
||||
scheduled_completion: {_gte: "${moment().startOf("day").toISOString()}",
|
||||
_lte: "${moment().endOf("day").toISOString()}"}}) {
|
||||
alt_transport
|
||||
clm_no
|
||||
jobid: id
|
||||
ins_co_nm
|
||||
iouparent
|
||||
ownerid
|
||||
ownr_co_nm
|
||||
ownr_ea
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
production_vars
|
||||
ro_number
|
||||
scheduled_completion
|
||||
suspended
|
||||
v_make_desc
|
||||
v_model_desc
|
||||
v_model_yr
|
||||
v_vin
|
||||
vehicleid
|
||||
|
||||
}
|
||||
`;
|
||||
@@ -1,6 +1,7 @@
|
||||
import Icon, { SyncOutlined } from "@ant-design/icons";
|
||||
import { gql, useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { Button, Dropdown, Menu, notification, Space } from "antd";
|
||||
import i18next from "i18next";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
@@ -37,12 +38,6 @@ import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
//Combination of the following:
|
||||
// /node_modules/react-grid-layout/css/styles.css
|
||||
// /node_modules/react-resizable/css/styles.css
|
||||
import DashboardScheduledInToday, {
|
||||
DashboardScheduledInTodayGql,
|
||||
} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component";
|
||||
import DashboardScheduledOutToday, {
|
||||
DashboardScheduledOutTodayGql,
|
||||
} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component";
|
||||
import "./dashboard-grid.styles.scss";
|
||||
import { GenerateDashboardData } from "./dashboard-grid.utils";
|
||||
|
||||
@@ -123,17 +118,15 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
|
||||
);
|
||||
const existingLayoutKeys = state.items.map((i) => i.i);
|
||||
const addComponentOverlay = (
|
||||
<Menu onClick={handleAddComponent}>
|
||||
{Object.keys(componentList).map((key) => (
|
||||
<Menu.Item
|
||||
key={key}
|
||||
value={key}
|
||||
disabled={existingLayoutKeys.includes(key)}
|
||||
>
|
||||
{componentList[key].label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
<Menu
|
||||
onClick={handleAddComponent}
|
||||
items={Object.keys(componentList).map((key) => ({
|
||||
key: key,
|
||||
value: key,
|
||||
disabled: existingLayoutKeys.includes(key),
|
||||
label: componentList[key].label,
|
||||
}))}
|
||||
></Menu>
|
||||
);
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
@@ -146,7 +139,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Dropdown overlay={addComponentOverlay} trigger={["click"]}>
|
||||
<Dropdown menu={addComponentOverlay} trigger={["click"]}>
|
||||
<Button>{t("dashboard.actions.addcomponent")}</Button>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
@@ -274,28 +267,6 @@ const componentList = {
|
||||
w: 2,
|
||||
h: 2,
|
||||
},
|
||||
ScheduleInToday: {
|
||||
label: i18next.t("dashboard.titles.scheduledintoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
}),
|
||||
component: DashboardScheduledInToday,
|
||||
gqlFragment: DashboardScheduledInTodayGql,
|
||||
minW: 10,
|
||||
minH: 2,
|
||||
w: 10,
|
||||
h: 2,
|
||||
},
|
||||
ScheduleOutToday: {
|
||||
label: i18next.t("dashboard.titles.scheduledouttoday", {
|
||||
date: moment().startOf("day").format("MM/DD/YYYY"),
|
||||
}),
|
||||
component: DashboardScheduledOutToday,
|
||||
gqlFragment: DashboardScheduledOutTodayGql,
|
||||
minW: 10,
|
||||
minH: 2,
|
||||
w: 10,
|
||||
h: 2,
|
||||
},
|
||||
};
|
||||
|
||||
const createDashboardQuery = (state) => {
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function DataLabel({
|
||||
<div {...props} style={{ display: "flex" }}>
|
||||
<div
|
||||
style={{
|
||||
// flex: 2,
|
||||
flex: 2,
|
||||
marginRight: ".2rem",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -67,7 +66,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
||||
key: "status",
|
||||
},
|
||||
{
|
||||
title: t("bills.fields.invoice_number"),
|
||||
title: t("jobs.fields.ro_number"),
|
||||
dataIndex: ["Posting", "Reference"],
|
||||
key: "reference",
|
||||
},
|
||||
@@ -118,7 +117,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
|
||||
}
|
||||
>
|
||||
<Table
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
rowKey={(record) => `${record.InvoiceNumber}${record.Account}`}
|
||||
dataSource={allocationsSummary}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import Dinero from "dinero.js";
|
||||
import { SyncOutlined } from "@ant-design/icons";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -95,7 +94,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
|
||||
<Alert type="warning" message={t("jobs.labels.dms.disablebillwip")} />
|
||||
)}
|
||||
<Table
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
rowKey="center"
|
||||
dataSource={allocationsSummary}
|
||||
|
||||
@@ -51,7 +51,7 @@ export function DmsCdkVehicles({ bodyshop, form, socket, job }) {
|
||||
<>
|
||||
<Modal
|
||||
width={"90%"}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
onOk={() => {
|
||||
form.setFieldsValue({
|
||||
|
||||
@@ -6,13 +6,10 @@ import {
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Menu,
|
||||
Select,
|
||||
InputNumber, Select,
|
||||
Space,
|
||||
Statistic,
|
||||
Switch,
|
||||
Typography,
|
||||
Typography
|
||||
} from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import moment from "moment";
|
||||
@@ -184,20 +181,6 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
<Space>
|
||||
<DmsCdkMakes form={form} socket={socket} job={job} />
|
||||
<DmsCdkMakesRefetch />
|
||||
<Form.Item
|
||||
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>
|
||||
)}
|
||||
@@ -292,36 +275,32 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
<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;
|
||||
menu={{
|
||||
items:
|
||||
bodyshop.cdk_configuration.controllist &&
|
||||
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,
|
||||
};
|
||||
}),
|
||||
});
|
||||
}}
|
||||
>
|
||||
{key.name}
|
||||
</Menu.Item>
|
||||
)
|
||||
)}
|
||||
</Menu>
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
controlnumber:
|
||||
key.controlnumber,
|
||||
};
|
||||
}),
|
||||
});
|
||||
},
|
||||
})
|
||||
),
|
||||
}}
|
||||
>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
<DownOutlined />
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Menu,
|
||||
Select,
|
||||
Space,
|
||||
Tabs,
|
||||
Upload,
|
||||
Space,
|
||||
Menu,
|
||||
Dropdown,
|
||||
Button,
|
||||
} from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import EmailDocumentsComponent from "../email-documents/email-documents.component";
|
||||
import _ from "lodash";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
|
||||
import EmailDocumentsComponent from "../email-documents/email-documents.component";
|
||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -54,51 +54,26 @@ export function EmailOverlayComponent({
|
||||
]),
|
||||
});
|
||||
};
|
||||
const handle_CC_Click = ({ item, key, keyPath }) => {
|
||||
const email = item.props.value;
|
||||
form.setFieldsValue({
|
||||
cc: _.uniq([
|
||||
...(form.getFieldValue("cc") || ""),
|
||||
...(typeof email === "string" ? [email] : email),
|
||||
]),
|
||||
});
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.employees
|
||||
.filter((e) => e.user_email)
|
||||
.map((e, idx) => (
|
||||
<Menu.Item value={e.user_email} key={idx}>
|
||||
{`${e.first_name} ${e.last_name}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
{bodyshop.md_to_emails.map((e, idx) => (
|
||||
<Menu.Item value={e.emails} key={idx + "group"}>
|
||||
{e.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
const menuCC = (
|
||||
<div>
|
||||
<Menu onClick={handle_CC_Click}>
|
||||
{bodyshop.employees
|
||||
.filter((e) => e.user_email)
|
||||
.map((e, idx) => (
|
||||
<Menu.Item value={e.user_email} key={idx}>
|
||||
{`${e.first_name} ${e.last_name}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
{bodyshop.md_to_emails.map((e, idx) => (
|
||||
<Menu.Item value={e.emails} key={idx + "group"}>
|
||||
{e.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
<Menu
|
||||
onClick={handleClick}
|
||||
items={[
|
||||
...bodyshop.employees
|
||||
.filter((e) => e.user_email)
|
||||
.map((e, idx) => ({
|
||||
value: e.user_email,
|
||||
key: idx,
|
||||
label: `${e.first_name} ${e.last_name}`,
|
||||
})),
|
||||
...bodyshop.md_to_emails.map((e, idx) => ({
|
||||
value: e.emails,
|
||||
key: idx + "group",
|
||||
label: e.label,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -129,7 +104,7 @@ export function EmailOverlayComponent({
|
||||
label={
|
||||
<Space>
|
||||
{t("emails.fields.to")}
|
||||
<Dropdown overlay={menu}>
|
||||
<Dropdown menu={menu}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
@@ -150,23 +125,7 @@ export function EmailOverlayComponent({
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<Form.Item label={t("emails.fields.cc")} name="cc">
|
||||
<Select mode="tags" tokenSeparators={[",", ";"]} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
|
||||
@@ -109,7 +109,7 @@ export function EmailOverlayContainer({
|
||||
]
|
||||
: []),
|
||||
],
|
||||
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.fullsize),
|
||||
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.src),
|
||||
//attachments,
|
||||
});
|
||||
notification["success"]({ message: t("emails.successes.sent") });
|
||||
@@ -174,7 +174,7 @@ export function EmailOverlayContainer({
|
||||
return (
|
||||
<Modal
|
||||
destroyOnClose={true}
|
||||
visible={modalVisible}
|
||||
open={modalVisible}
|
||||
maskClosable={false}
|
||||
width={"80%"}
|
||||
onOk={() => form.submit()}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Select, Space, Tag } from "antd";
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const { Option } = Select;
|
||||
//To be used as a form element only.
|
||||
|
||||
const EmployeeSearchSelect = ({ options, ...props }) => {
|
||||
const EmployeeSearchSelect = ({ options, ...props }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -39,4 +39,4 @@ const EmployeeSearchSelect = ({ options, ...props }) => {
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
export default EmployeeSearchSelect;
|
||||
export default forwardRef(EmployeeSearchSelect);
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Select } from "antd";
|
||||
import React, { forwardRef } from "react";
|
||||
import { QUERY_TEAMS } from "../../graphql/employee_teams.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
|
||||
//To be used as a form element only.
|
||||
|
||||
const EmployeeTeamSearchSelect = ({ ...props }, ref) => {
|
||||
const { loading, error, data } = useQuery(QUERY_TEAMS);
|
||||
|
||||
if (error) return <AlertComponent message={JSON.stringify(error)} />;
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
allowClear
|
||||
loading={loading}
|
||||
style={{
|
||||
width: 400,
|
||||
}}
|
||||
options={
|
||||
data
|
||||
? data.employee_teams.map((e) => ({
|
||||
value: JSON.stringify(e),
|
||||
label: e.name,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default forwardRef(EmployeeTeamSearchSelect);
|
||||
@@ -40,22 +40,22 @@ class ErrorBoundary extends React.Component {
|
||||
}
|
||||
|
||||
handleErrorSubmit = () => {
|
||||
// window.$crisp.push([
|
||||
// "do",
|
||||
// "message:send",
|
||||
// [
|
||||
// "text",
|
||||
// `I hit the following error: \n\n
|
||||
// ${this.state.error.message}\n\n
|
||||
// ${this.state.error.stack}\n\n
|
||||
// URL:${window.location} as ${this.props.currentUser.email} for ${
|
||||
// this.props.bodyshop && this.props.bodyshop.name
|
||||
// }
|
||||
// `,
|
||||
// ],
|
||||
// ]);
|
||||
window.$crisp.push([
|
||||
"do",
|
||||
"message:send",
|
||||
[
|
||||
"text",
|
||||
`I hit the following error: \n\n
|
||||
${this.state.error.message}\n\n
|
||||
${this.state.error.stack}\n\n
|
||||
URL:${window.location} as ${this.props.currentUser.email} for ${
|
||||
this.props.bodyshop && this.props.bodyshop.name
|
||||
}
|
||||
`,
|
||||
],
|
||||
]);
|
||||
|
||||
// window.$crisp.push(["do", "chat:open"]);
|
||||
window.$crisp.push(["do", "chat:open"]);
|
||||
// const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue**
|
||||
|
||||
// ----
|
||||
|
||||
@@ -65,17 +65,8 @@ export function FormDatePicker({
|
||||
});
|
||||
}
|
||||
|
||||
if (_a.isValid() && onChange) {
|
||||
if (onlyFuture) {
|
||||
if (moment().subtract(1, "day").isBefore(_a)) {
|
||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||
} else {
|
||||
onChange(isDateOnly ? moment().format("YYYY-MM-DD") : moment());
|
||||
}
|
||||
} else {
|
||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||
}
|
||||
}
|
||||
if (_a.isValid() && onChange)
|
||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { forwardRef } from "react";
|
||||
//import DatePicker from "react-datepicker";
|
||||
//import "react-datepicker/src/stylesheets/datepicker.scss";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import { TimePicker } from "antd";
|
||||
import moment from "moment";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
//To be used as a form element only.
|
||||
|
||||
const DateTimePicker = (
|
||||
@@ -26,21 +26,20 @@ const DateTimePicker = (
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onChange={onChange}
|
||||
onlyFuture={onlyFuture}
|
||||
isDateOnly={false}
|
||||
/>
|
||||
|
||||
<TimePicker
|
||||
value={value ? moment(value) : null}
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => moment().isAfter(d),
|
||||
})}
|
||||
onChange={onChange}
|
||||
showSecond={false}
|
||||
minuteStep={15}
|
||||
onBlur={onBlur}
|
||||
format="hh:mm a"
|
||||
{...restProps}
|
||||
value={value ? moment(value) : null}
|
||||
{...(onlyFuture && {
|
||||
disabledDate: (d) => moment().isAfter(d),
|
||||
})}
|
||||
onChange={onChange}
|
||||
showSecond={false}
|
||||
minuteStep={15}
|
||||
onBlur={onBlur}
|
||||
format="hh:mm a"
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -94,7 +94,7 @@ const FormInputNUmberCalculator = (
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Popover content={popContent} visible={history.length > 0}>
|
||||
<Popover content={popContent} open={history.length > 0}>
|
||||
<InputNumber
|
||||
ref={ref}
|
||||
value={value}
|
||||
|
||||
@@ -1,26 +1,9 @@
|
||||
import Dinero from "dinero.js";
|
||||
import React, { forwardRef } from "react";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
const ReadOnlyFormItem = (
|
||||
{ bodyshop, value, type = "text", onChange },
|
||||
ref
|
||||
) => {
|
||||
const ReadOnlyFormItem = ({ value, type = "text", onChange }, ref) => {
|
||||
if (!value) return null;
|
||||
switch (type) {
|
||||
case "employee":
|
||||
const emp = bodyshop.employees.find((e) => e.id === value);
|
||||
return `${emp?.first_name} ${emp?.last_name}`;
|
||||
|
||||
case "text":
|
||||
return <div>{value}</div>;
|
||||
case "currency":
|
||||
@@ -31,8 +14,4 @@ const ReadOnlyFormItem = (
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(forwardRef(ReadOnlyFormItem));
|
||||
export default forwardRef(ReadOnlyFormItem);
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
import { AutoComplete, Divider, Input, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useHistory } from "react-router-dom";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay, {
|
||||
OwnerNameDisplayFunction,
|
||||
} from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
|
||||
export default function GlobalSearchOs() {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(false);
|
||||
|
||||
const executeSearch = async (v) => {
|
||||
if (v && v && v !== "" && v.length >= 3) {
|
||||
try {
|
||||
setLoading(true);
|
||||
const searchData = await axios.post("/search", {
|
||||
search: v,
|
||||
});
|
||||
|
||||
const resultsByType = {
|
||||
payments: [],
|
||||
jobs: [],
|
||||
bills: [],
|
||||
owners: [],
|
||||
vehicles: [],
|
||||
};
|
||||
|
||||
searchData.data.hits.hits.forEach((hit) => {
|
||||
resultsByType[hit._index].push(hit._source);
|
||||
});
|
||||
setData([
|
||||
{
|
||||
label: renderTitle(t("menus.header.search.jobs")),
|
||||
options: resultsByType.jobs.map((job) => {
|
||||
return {
|
||||
key: job.id,
|
||||
value: job.ro_number || "N/A",
|
||||
label: (
|
||||
<Link to={`/manage/jobs/${job.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
<strong>{job.ro_number || t("general.labels.na")}</strong>
|
||||
<span>{`${job.status || ""}`}</span>
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={job} />
|
||||
</span>
|
||||
<span>{`${job.v_model_yr || ""} ${
|
||||
job.v_make_desc || ""
|
||||
} ${job.v_model_desc || ""}`}</span>
|
||||
<span>{`${job.clm_no || ""}`}</span>
|
||||
<span>{`${job.plate_no || ""}`}</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: renderTitle(t("menus.header.search.owners")),
|
||||
options: resultsByType.owners.map((owner) => {
|
||||
return {
|
||||
key: owner.id,
|
||||
value: OwnerNameDisplayFunction(owner),
|
||||
label: (
|
||||
<Link to={`/manage/owners/${owner.id}`}>
|
||||
<Space
|
||||
size="small"
|
||||
split={<Divider type="vertical" />}
|
||||
wrap
|
||||
>
|
||||
<span>
|
||||
<OwnerNameDisplay ownerObject={owner} />
|
||||
</span>
|
||||
<PhoneNumberFormatter>
|
||||
{owner.ownr_ph1}
|
||||
</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter>
|
||||
{owner.ownr_ph2}
|
||||
</PhoneNumberFormatter>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: renderTitle(t("menus.header.search.vehicles")),
|
||||
options: resultsByType.vehicles.map((vehicle) => {
|
||||
return {
|
||||
key: vehicle.id,
|
||||
value: `${vehicle.v_model_yr || ""} ${
|
||||
vehicle.v_make_desc || ""
|
||||
} ${vehicle.v_model_desc || ""}`,
|
||||
label: (
|
||||
<Link to={`/manage/vehicles/${vehicle.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
<span>
|
||||
{`${vehicle.v_model_yr || ""} ${
|
||||
vehicle.v_make_desc || ""
|
||||
} ${vehicle.v_model_desc || ""}`}
|
||||
</span>
|
||||
<span>{vehicle.plate_no || ""}</span>
|
||||
<span>
|
||||
<VehicleVinDisplay>
|
||||
{vehicle.v_vin || ""}
|
||||
</VehicleVinDisplay>
|
||||
</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: renderTitle(t("menus.header.search.payments")),
|
||||
options: resultsByType.payments.map((payment) => {
|
||||
return {
|
||||
key: payment.id,
|
||||
value: `${payment.job?.ro_number} ${payment.amount}`,
|
||||
label: (
|
||||
<Link to={`/manage/jobs/${payment.job?.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
<span>{payment.paymentnum}</span>
|
||||
<span>{payment.job?.ro_number}</span>
|
||||
<span>{payment.memo || ""}</span>
|
||||
<span>{payment.amount || ""}</span>
|
||||
<span>{payment.transactionid || ""}</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: renderTitle(t("menus.header.search.bills")),
|
||||
options: resultsByType.bills.map((bill) => {
|
||||
return {
|
||||
key: bill.id,
|
||||
value: `${bill.invoice_number} - ${bill.vendor.name}`,
|
||||
label: (
|
||||
<Link to={`/manage/bills?billid=${bill.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
<span>{bill.invoice_number}</span>
|
||||
<span>{bill.vendor.name}</span>
|
||||
<span>{bill.date}</span>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
};
|
||||
}),
|
||||
},
|
||||
// {
|
||||
// label: renderTitle(t("menus.header.search.phonebook")),
|
||||
// options: resultsByType.search_phonebook.map((pb) => {
|
||||
// return {
|
||||
// key: pb.id,
|
||||
// value: `${pb.firstname || ""} ${pb.lastname || ""} ${
|
||||
// pb.company || ""
|
||||
// }`,
|
||||
// label: (
|
||||
// <Link to={`/manage/phonebook?phonebookentry=${pb.id}`}>
|
||||
// <Space size="small" split={<Divider type="vertical" />}>
|
||||
// <span>{`${pb.firstname || ""} ${pb.lastname || ""} ${
|
||||
// pb.company || ""
|
||||
// }`}</span>
|
||||
// <PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter>
|
||||
// <span>{pb.email}</span>
|
||||
// </Space>
|
||||
// </Link>
|
||||
// ),
|
||||
// };
|
||||
// }),
|
||||
// },
|
||||
]);
|
||||
} catch (error) {
|
||||
console.log("Error while fetching search results", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
debouncedExecuteSearch(value);
|
||||
};
|
||||
|
||||
const renderTitle = (title) => {
|
||||
return <span>{title}</span>;
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoComplete
|
||||
options={data}
|
||||
onSearch={handleSearch}
|
||||
defaultActiveFirstOption
|
||||
onSelect={(val, opt) => {
|
||||
history.push(opt.label.props.to);
|
||||
}}
|
||||
onClear={() => setData([])}
|
||||
>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
enterButton
|
||||
allowClear
|
||||
loading={loading}
|
||||
/>
|
||||
</AutoComplete>
|
||||
);
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import OwnerNameDisplay, {
|
||||
OwnerNameDisplayFunction,
|
||||
OwnerNameDisplayFunction
|
||||
} from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
export default function GlobalSearch() {
|
||||
@@ -18,18 +18,11 @@ export default function GlobalSearch() {
|
||||
useLazyQuery(GLOBAL_SEARCH_QUERY);
|
||||
|
||||
const executeSearch = (v) => {
|
||||
if (
|
||||
v &&
|
||||
v.variables.search &&
|
||||
v.variables.search !== "" &&
|
||||
v.variables.search.length >= 3
|
||||
)
|
||||
callSearch(v);
|
||||
if (v && v.variables.search && v.variables.search !== "") callSearch(v);
|
||||
};
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
console.log("Handle Search");
|
||||
debouncedExecuteSearch({ variables: { search: value } });
|
||||
};
|
||||
|
||||
@@ -44,7 +37,7 @@ export default function GlobalSearch() {
|
||||
options: data.search_jobs.map((job) => {
|
||||
return {
|
||||
key: job.id,
|
||||
value: job.ro_number || "N/A",
|
||||
value: job.ro_number,
|
||||
label: (
|
||||
<Link to={`/manage/jobs/${job.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import Icon, {
|
||||
BankFilled,
|
||||
BarChartOutlined,
|
||||
CarFilled,
|
||||
CheckCircleOutlined,
|
||||
ClockCircleFilled,
|
||||
CheckCircleOutlined,
|
||||
DashboardFilled,
|
||||
DollarCircleFilled,
|
||||
ExportOutlined,
|
||||
@@ -25,7 +26,6 @@ import Icon, {
|
||||
UnorderedListOutlined,
|
||||
UserOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Layout, Menu } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -70,8 +70,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
setReportCenterContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "reportCenter" })),
|
||||
signOutStart: () => dispatch(signOutStart()),
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||
});
|
||||
|
||||
function Header({
|
||||
@@ -85,7 +83,6 @@ function Header({
|
||||
setPaymentContext,
|
||||
setReportCenterContext,
|
||||
recentItems,
|
||||
setCardPaymentContext,
|
||||
}) {
|
||||
const { Simple_Inventory } = useTreatments(
|
||||
["Simple_Inventory"],
|
||||
@@ -97,11 +94,6 @@ function Header({
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
const { ImEXPay } = useTreatments(
|
||||
["ImEXPay"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -112,325 +104,432 @@ function Header({
|
||||
//theme="light"
|
||||
theme={"dark"}
|
||||
selectedKeys={[selectedHeader]}
|
||||
onClick={handleMenuClick}
|
||||
onClick={(e) => {
|
||||
handleMenuClick(e);
|
||||
}}
|
||||
subMenuCloseDelay={0.3}
|
||||
>
|
||||
<Menu.Item key="home" icon={<HomeFilled />}>
|
||||
<Link to="/manage">{t("menus.header.home")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="schedule" icon={<Icon component={FaCalendarAlt} />}>
|
||||
<Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu
|
||||
key="jobssubmenu"
|
||||
icon={<Icon component={FaCarCrash} />}
|
||||
title={t("menus.header.jobs")}
|
||||
>
|
||||
<Menu.Item key="activejobs" icon={<FileFilled />}>
|
||||
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="readyjobs" icon={<CheckCircleOutlined />}>
|
||||
<Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="parts-queue" icon={<ToolFilled />}>
|
||||
<Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="availablejobs" icon={<ImportOutlined />}>
|
||||
<Link to="/manage/available">
|
||||
{t("menus.header.availablejobs")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="newjob" icon={<FileAddOutlined />}>
|
||||
<Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Divider key="div1" />
|
||||
<Menu.Item key="alljobs" icon={<UnorderedListOutlined />}>
|
||||
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Divider key="div2" />
|
||||
<Menu.Item key="productionlist" icon={<ScheduleOutlined />}>
|
||||
<Link to="/manage/production/list">
|
||||
{t("menus.header.productionlist")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="productionboard" icon={<Icon component={BsKanban} />}>
|
||||
<Link to="/manage/production/board">
|
||||
{t("menus.header.productionboard")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Divider key="div3" />
|
||||
<Menu.Item key="scoreboard" icon={<LineChartOutlined />}>
|
||||
<Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu
|
||||
key="customers"
|
||||
icon={<UserOutlined />}
|
||||
title={t("menus.header.customers")}
|
||||
>
|
||||
<Menu.Item key="owners" icon={<TeamOutlined />}>
|
||||
<Link to="/manage/owners">{t("menus.header.owners")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="vehicles" icon={<CarFilled />}>
|
||||
<Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu
|
||||
key="ccs"
|
||||
icon={<CarFilled />}
|
||||
title={t("menus.header.courtesycars")}
|
||||
>
|
||||
<Menu.Item key="courtesycarsall" icon={<CarFilled />}>
|
||||
<Link to="/manage/courtesycars">
|
||||
{t("menus.header.courtesycars-all")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="contracts" icon={<FileFilled />}>
|
||||
<Link to="/manage/courtesycars/contracts">
|
||||
{t("menus.header.courtesycars-contracts")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="newcontract" icon={<FileAddFilled />}>
|
||||
<Link to="/manage/courtesycars/contracts/new">
|
||||
{t("menus.header.courtesycars-newcontract")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu
|
||||
key="accounting"
|
||||
icon={<DollarCircleFilled />}
|
||||
title={t("menus.header.accounting")}
|
||||
>
|
||||
<Menu.Item
|
||||
key="bills"
|
||||
icon={<Icon component={FaFileInvoiceDollar} />}
|
||||
>
|
||||
<Link to="/manage/bills">{t("menus.header.bills")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="enterbills"
|
||||
icon={<Icon component={GiPayMoney} />}
|
||||
onClick={() => {
|
||||
setBillEnterContext({
|
||||
actions: {},
|
||||
context: {},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("menus.header.enterbills")}
|
||||
</Menu.Item>
|
||||
{Simple_Inventory.treatment === "on" && (
|
||||
<>
|
||||
<Menu.Divider key="div4" />
|
||||
<Menu.Item
|
||||
key="inventory"
|
||||
icon={<Icon component={FaFileInvoiceDollar} />}
|
||||
>
|
||||
<Link to="/manage/inventory">
|
||||
{t("menus.header.inventory")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</>
|
||||
)}
|
||||
<Menu.Divider key="div7" />
|
||||
<Menu.Item key="allpayments" icon={<BankFilled />}>
|
||||
<Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="enterpayments"
|
||||
onClick={() => {
|
||||
setPaymentContext({
|
||||
actions: {},
|
||||
context: null,
|
||||
});
|
||||
}}
|
||||
icon={<Icon component={FaCreditCard} />}
|
||||
>
|
||||
{t("menus.header.enterpayment")}
|
||||
</Menu.Item>
|
||||
{ImEXPay.treatment === "on" && (
|
||||
<Menu.Item
|
||||
key="entercardpayments"
|
||||
onClick={() => {
|
||||
setCardPaymentContext({
|
||||
actions: {},
|
||||
context: {},
|
||||
});
|
||||
}}
|
||||
icon={<Icon component={FaCreditCard} />}
|
||||
>
|
||||
{t("menus.header.entercardpayment")}
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Divider key="div5" />
|
||||
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
|
||||
<Link to="/manage/timetickets">
|
||||
{t("menus.header.timetickets")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
{bodyshop?.md_tasks_presets?.use_approvals && (
|
||||
<Menu.Item key="ttapprovals" icon={<FieldTimeOutlined />}>
|
||||
<Link to="/manage/ttapprovals">
|
||||
{t("menus.header.ttapprovals")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item
|
||||
key="entertimetickets"
|
||||
icon={<Icon component={GiPlayerTime} />}
|
||||
onClick={() => {
|
||||
setTimeTicketContext({
|
||||
actions: {},
|
||||
context: {
|
||||
created_by: currentUser.displayName
|
||||
? currentUser.email.concat(" | ", currentUser.displayName)
|
||||
: currentUser.email,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("menus.header.entertimeticket")}
|
||||
</Menu.Item>
|
||||
<Menu.Divider key="div6" />
|
||||
<Menu.SubMenu
|
||||
key="accountingexport"
|
||||
title={t("menus.header.export")}
|
||||
icon={<ExportOutlined />}
|
||||
>
|
||||
<Menu.Item key="receivables">
|
||||
<Link to="/manage/accounting/receivables">
|
||||
{t("menus.header.accounting-receivables")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
{(!(
|
||||
(bodyshop && bodyshop.cdk_dealerid) ||
|
||||
(bodyshop && bodyshop.pbs_serialnumber)
|
||||
) ||
|
||||
DmsAp.treatment === "on") && (
|
||||
<Menu.Item key="payables">
|
||||
<Link to="/manage/accounting/payables">
|
||||
{t("menus.header.accounting-payables")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
{!(
|
||||
(bodyshop && bodyshop.cdk_dealerid) ||
|
||||
(bodyshop && bodyshop.pbs_serialnumber)
|
||||
) && (
|
||||
<Menu.Item key="payments">
|
||||
<Link to="/manage/accounting/payments">
|
||||
{t("menus.header.accounting-payments")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item key="export-logs">
|
||||
<Link to="/manage/accounting/exportlogs">
|
||||
{t("menus.header.export-logs")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
</Menu.SubMenu>
|
||||
<Menu.Item key="phonebook" icon={<PhoneOutlined />}>
|
||||
<Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="temporarydocs" icon={<PaperClipOutlined />}>
|
||||
<Link to="/manage/temporarydocs">
|
||||
{t("menus.header.temporarydocs")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.SubMenu
|
||||
key="shopsubmenu"
|
||||
title={t("menus.header.shop")}
|
||||
icon={<SettingOutlined />}
|
||||
>
|
||||
<Menu.Item key="shop" icon={<Icon component={GiSettingsKnobs} />}>
|
||||
<Link to="/manage/shop?tab=info">
|
||||
{t("menus.header.shop_config")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="dashboard" icon={<DashboardFilled />}>
|
||||
<Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="reportcenter"
|
||||
icon={<BarChartOutlined />}
|
||||
onClick={() => {
|
||||
setReportCenterContext({
|
||||
actions: {},
|
||||
context: {},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("menus.header.reportcenter")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="shop-vendors"
|
||||
icon={<Icon component={IoBusinessOutline} />}
|
||||
>
|
||||
<Link to="/manage/shop/vendors">
|
||||
{t("menus.header.shop_vendors")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="shop-csi" icon={<Icon component={RiSurveyLine} />}>
|
||||
<Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
|
||||
</Menu.Item>
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu
|
||||
key="user"
|
||||
title={
|
||||
currentUser.displayName ||
|
||||
currentUser.email ||
|
||||
t("general.labels.unknown")
|
||||
}
|
||||
>
|
||||
<Menu.Item key="signout" danger onClick={() => signOutStart()}>
|
||||
{t("user.actions.signout")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
key="help"
|
||||
onClick={() => {
|
||||
window.open("https://rometech.com/", "_blank");
|
||||
}}
|
||||
icon={<Icon component={QuestionCircleFilled} />}
|
||||
>
|
||||
{t("menus.header.help")}
|
||||
</Menu.Item>
|
||||
<Menu.Item key="shiftclock">
|
||||
<Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="profile">
|
||||
<Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
|
||||
</Menu.Item>
|
||||
items={[
|
||||
{
|
||||
// <Menu.SubMenu
|
||||
// key="langselecter"
|
||||
// title={
|
||||
// <span>
|
||||
// <GlobalOutlined />
|
||||
// <span>{t("menus.currentuser.languageselector")}</span>
|
||||
// </span>
|
||||
// }
|
||||
// >
|
||||
// <Menu.Item actiontype="lang-select" key="en-US">
|
||||
// {t("general.languages.english")}
|
||||
// </Menu.Item>
|
||||
// <Menu.Item actiontype="lang-select" key="fr-CA">
|
||||
// {t("general.languages.french")}
|
||||
// </Menu.Item>
|
||||
// <Menu.Item actiontype="lang-select" key="es-MX">
|
||||
// {t("general.languages.spanish")}
|
||||
// </Menu.Item>
|
||||
// </Menu.SubMenu>
|
||||
}
|
||||
</Menu.SubMenu>
|
||||
<Menu.SubMenu key="recent" title={<ClockCircleFilled />}>
|
||||
{recentItems.map((i, idx) => (
|
||||
<Menu.Item key={idx}>
|
||||
<Link to={i.url}>{i.label}</Link>
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
</Menu>
|
||||
key: "home",
|
||||
icon: <HomeFilled />,
|
||||
label: <Link to="/manage">{t("menus.header.home")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "schedule",
|
||||
icon: <Icon component={FaCalendarAlt} />,
|
||||
label: (
|
||||
<Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "jobssubmenu",
|
||||
icon: <Icon component={FaCarCrash} />,
|
||||
label: t("menus.header.jobs"),
|
||||
children: [
|
||||
{
|
||||
key: "activejobs",
|
||||
icon: <FileFilled />,
|
||||
label: (
|
||||
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "readyjobs",
|
||||
icon: <CheckCircleOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/jobs/ready">
|
||||
{t("menus.header.readyjobs")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "parts-queue",
|
||||
icon: <ToolFilled />,
|
||||
label: (
|
||||
<Link to="/manage/partsqueue">
|
||||
{t("menus.header.parts-queue")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "availablejobs",
|
||||
icon: <ImportOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/available">
|
||||
{t("menus.header.availablejobs")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "newjob",
|
||||
icon: <FileAddOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
|
||||
),
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "alljobs",
|
||||
icon: <UnorderedListOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
|
||||
),
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "productionlist",
|
||||
icon: <ScheduleOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/production/list">
|
||||
{t("menus.header.productionlist")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "productionboard",
|
||||
icon: <Icon component={BsKanban} />,
|
||||
label: (
|
||||
<Link to="/manage/production/board">
|
||||
{t("menus.header.productionboard")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "scoreboard",
|
||||
icon: <LineChartOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/scoreboard">
|
||||
{t("menus.header.scoreboard")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "customers",
|
||||
icon: <UserOutlined />,
|
||||
label: t("menus.header.customers"),
|
||||
children: [
|
||||
{
|
||||
key: "owners",
|
||||
icon: <TeamOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/owners">{t("menus.header.owners")}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "vehicles",
|
||||
icon: <CarFilled />,
|
||||
label: (
|
||||
<Link to="/manage/vehicles">
|
||||
{t("menus.header.vehicles")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "ccs",
|
||||
icon: <CarFilled />,
|
||||
label: t("menus.header.courtesycars"),
|
||||
children: [
|
||||
{
|
||||
key: "courtesycarsall",
|
||||
icon: <CarFilled />,
|
||||
label: (
|
||||
<Link to="/manage/courtesycars">
|
||||
{t("menus.header.courtesycars-all")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "contracts",
|
||||
icon: <FileFilled />,
|
||||
label: (
|
||||
<Link to="/manage/courtesycars/contracts">
|
||||
{t("menus.header.courtesycars-contracts")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "newcontract",
|
||||
icon: <FileAddFilled />,
|
||||
label: (
|
||||
<Link to="/manage/courtesycars/contracts/new">
|
||||
{t("menus.header.courtesycars-newcontract")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "accounting",
|
||||
icon: <DollarCircleFilled />,
|
||||
label: t("menus.header.accounting"),
|
||||
children: [
|
||||
{
|
||||
key: "bills",
|
||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
||||
label: (
|
||||
<Link to="/manage/bills">{t("menus.header.bills")}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "enterbills",
|
||||
icon: <Icon component={GiPayMoney} />,
|
||||
onClick: () => {
|
||||
setBillEnterContext({
|
||||
actions: {},
|
||||
context: {},
|
||||
});
|
||||
},
|
||||
label: t("menus.header.enterbills"),
|
||||
},
|
||||
...(Simple_Inventory.treatment === "on"
|
||||
? [
|
||||
{
|
||||
type: "divider",
|
||||
},
|
||||
{
|
||||
key: "inventory",
|
||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
||||
label: (
|
||||
<Link to="/manage/inventory">
|
||||
{t("menus.header.inventory")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "allpayments",
|
||||
icon: <BankFilled />,
|
||||
label: (
|
||||
<Link to="/manage/payments">
|
||||
{t("menus.header.allpayments")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "enterpayments",
|
||||
onClick: () => {
|
||||
setPaymentContext({
|
||||
actions: {},
|
||||
context: null,
|
||||
});
|
||||
},
|
||||
icon: <Icon component={FaCreditCard} />,
|
||||
label: t("menus.header.enterpayment"),
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "timetickets",
|
||||
icon: <FieldTimeOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/timetickets">
|
||||
{t("menus.header.timetickets")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "entertimetickets",
|
||||
icon: <Icon component={GiPlayerTime} />,
|
||||
onClick: () => {
|
||||
setTimeTicketContext({
|
||||
actions: {},
|
||||
context: {},
|
||||
});
|
||||
},
|
||||
label: t("menus.header.entertimeticket"),
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "accountingexport",
|
||||
icon: <ExportOutlined />,
|
||||
|
||||
label: t("menus.header.export"),
|
||||
children: [
|
||||
{
|
||||
key: "receivables",
|
||||
label: (
|
||||
<Link to="/manage/accounting/receivables">
|
||||
{t("menus.header.accounting-receivables")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
...(!(
|
||||
(bodyshop && bodyshop.cdk_dealerid) ||
|
||||
(bodyshop && bodyshop.pbs_serialnumber)
|
||||
) || DmsAp.treatment === "on"
|
||||
? [
|
||||
{
|
||||
key: "payables",
|
||||
label: (
|
||||
<Link to="/manage/accounting/payables">
|
||||
{t("menus.header.accounting-payables")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
...(!(
|
||||
(bodyshop && bodyshop.cdk_dealerid) ||
|
||||
(bodyshop && bodyshop.pbs_serialnumber)
|
||||
)
|
||||
? [
|
||||
{
|
||||
key: "payments",
|
||||
label: (
|
||||
<Link to="/manage/accounting/payments">
|
||||
{t("menus.header.accounting-payments")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "export-logs",
|
||||
label: (
|
||||
<Link to="/manage/accounting/exportlogs">
|
||||
{t("menus.header.export-logs")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
key: "phonebook",
|
||||
icon: <PhoneOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "temporarydocs",
|
||||
icon: <PaperClipOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/temporarydocs">
|
||||
{t("menus.header.temporarydocs")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
key: "shopsubmenu",
|
||||
icon: <SettingOutlined />,
|
||||
label: t("menus.header.shop"),
|
||||
children: [
|
||||
{
|
||||
key: "shop",
|
||||
icon: <Icon component={GiSettingsKnobs} />,
|
||||
label: (
|
||||
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "dashboard",
|
||||
icon: <DashboardFilled />,
|
||||
label: (
|
||||
<Link to="/manage/dashboard">
|
||||
{t("menus.header.dashboard")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "reportcenter",
|
||||
icon: <BarChartOutlined />,
|
||||
onClick: () => {
|
||||
setReportCenterContext({
|
||||
actions: {},
|
||||
context: {},
|
||||
});
|
||||
},
|
||||
label: t("menus.header.reportcenter"),
|
||||
},
|
||||
{
|
||||
key: "shop-vendors",
|
||||
icon: <Icon component={IoBusinessOutline} />,
|
||||
label: (
|
||||
<Link to="/manage/shop/vendors">
|
||||
{t("menus.header.shop_vendors")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "shop-csi",
|
||||
icon: <Icon component={RiSurveyLine} />,
|
||||
label: (
|
||||
<Link to="/manage/shop/csi">
|
||||
{t("menus.header.shop_csi")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "user",
|
||||
label:
|
||||
currentUser.displayName ||
|
||||
currentUser.email ||
|
||||
t("general.labels.unknown"),
|
||||
children: [
|
||||
{
|
||||
key: "signout",
|
||||
danger: true,
|
||||
onClick: () => signOutStart(),
|
||||
label: t("user.actions.signout"),
|
||||
},
|
||||
{
|
||||
key: "help",
|
||||
icon: <Icon component={QuestionCircleFilled} />,
|
||||
onClick: () => {
|
||||
window.open("https://help.imex.online/", "_blank");
|
||||
},
|
||||
label: t("menus.header.help"),
|
||||
},
|
||||
{
|
||||
key: "rescue",
|
||||
onClick: () => {
|
||||
window.open("https://imexrescue.com/", "_blank");
|
||||
},
|
||||
label: t("menus.header.rescueme"),
|
||||
},
|
||||
{
|
||||
key: "shiftclock",
|
||||
label: (
|
||||
<Link to="/manage/shiftclock">
|
||||
{t("menus.header.shiftclock")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "profile",
|
||||
label: (
|
||||
<Link to="/manage/profile">
|
||||
{t("menus.currentuser.profile")}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "recent",
|
||||
label: <ClockCircleFilled />,
|
||||
children: recentItems.map((i, idx) => ({
|
||||
key: idx,
|
||||
label: <Link to={i.url}>{i.label}</Link>,
|
||||
})),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Layout.Header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,18 +11,18 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export function HeaderContainer({ setUserLanguage }) {
|
||||
const handleMenuClick = (e) => {
|
||||
if (e.item.props.actiontype === "lang-select") {
|
||||
i18next.changeLanguage(e.key, (err, t) => {
|
||||
if (err) {
|
||||
logImEXEvent("language_change_error", { error: err });
|
||||
|
||||
return console.log("Error encountered when changing languages.", err);
|
||||
}
|
||||
logImEXEvent("language_change", { language: e.key });
|
||||
|
||||
setUserLanguage(e.key);
|
||||
});
|
||||
}
|
||||
console.log("Language Switching is currently disabled.");
|
||||
// TODO Enable language switching
|
||||
// if (e.item.props.actiontype === "lang-select") {
|
||||
// i18next.changeLanguage(e.key, (err, t) => {
|
||||
// if (err) {
|
||||
// logImEXEvent("language_change_error", { error: err });
|
||||
// return console.log("Error encountered when changing languages.", err);
|
||||
// }
|
||||
// logImEXEvent("language_change", { language: e.key });
|
||||
// setUserLanguage(e.key);
|
||||
// });
|
||||
// }
|
||||
};
|
||||
|
||||
return <HeaderComponent handleMenuClick={handleMenuClick} />;
|
||||
|
||||
@@ -11,7 +11,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component";
|
||||
import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -214,7 +213,7 @@ export function JobsList({
|
||||
loading={loading}
|
||||
pagination={{
|
||||
position: "top",
|
||||
pageSize: pageLimit,
|
||||
pageSize: 25,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
}}
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from "../../redux/application/application.actions";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import InventoryListPaginated from "./inventory-list.component";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//bodyshop: selectBodyshop,
|
||||
@@ -33,8 +32,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) {
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
search: search || "",
|
||||
offset: page ? (page - 1) * pageLimit : 0,
|
||||
limit: pageLimit,
|
||||
offset: page ? (page - 1) * 25 : 0,
|
||||
limit: 25,
|
||||
consumedIsNull: showall === "true" ? null : true,
|
||||
order: [
|
||||
{
|
||||
|
||||
@@ -103,7 +103,7 @@ export function InventoryUpsertModalContainer({
|
||||
? t("inventory.actions.edit")
|
||||
: t("inventory.actions.new")
|
||||
}
|
||||
visible={visible}
|
||||
open={visible}
|
||||
okText={t("general.actions.save")}
|
||||
onOk={() => {
|
||||
form.submit();
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR } from "../../graphql/vendors.queries";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
@@ -14,14 +13,13 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal);
|
||||
|
||||
export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
|
||||
export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
@@ -98,7 +96,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
|
||||
return (
|
||||
<>
|
||||
<Button onClick={showModal}>{t("printcenter.jobs.3rdpartypayer")}</Button>
|
||||
<Modal visible={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
|
||||
<Modal open={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
|
||||
<Form
|
||||
onFinish={handleFinish}
|
||||
autoComplete={"off"}
|
||||
@@ -214,9 +212,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
|
||||
]}
|
||||
>
|
||||
<Radio.Group>
|
||||
{!technician ? (
|
||||
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
|
||||
) : null}
|
||||
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
|
||||
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { Dropdown, notification } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { Dropdown, Menu, notification } from "antd";
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -37,18 +37,19 @@ export function JobAltTransportChange({ bodyshop, job }) {
|
||||
});
|
||||
}
|
||||
};
|
||||
const menu = (
|
||||
<Menu selectedKeys={[job && job.alt_transport]} onClick={onClick}>
|
||||
{bodyshop.appt_alt_transport &&
|
||||
bodyshop.appt_alt_transport.map((alt) => (
|
||||
<Menu.Item key={alt}>{alt}</Menu.Item>
|
||||
))}
|
||||
<Menu.Divider />
|
||||
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
const menu = [
|
||||
...(bodyshop.appt_alt_transport &&
|
||||
bodyshop.appt_alt_transport.map((alt) => ({
|
||||
key: alt,
|
||||
label: alt,
|
||||
value: alt,
|
||||
onClick,
|
||||
}))),
|
||||
{ type: "divider" },
|
||||
{ key: "null", label: t("general.actions.clear"), onClick },
|
||||
];
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<Dropdown menu={{ items: menu }}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
<DownOutlined />
|
||||
</a>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from "react";
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import { Dropdown, notification } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { Dropdown, Menu, notification } from "antd";
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -44,21 +44,23 @@ export function ScheduleEventColor({ bodyshop, event }) {
|
||||
bodyshop.appt_colors.filter((color) => color.color.hex === event.color)[0]
|
||||
?.label;
|
||||
|
||||
const menu = (
|
||||
<Menu selectedKeys={[event.color]} onClick={onClick}>
|
||||
{bodyshop.appt_colors &&
|
||||
bodyshop.appt_colors.map((color) => (
|
||||
<Menu.Item style={{ color: color.color.hex }} key={color.color.hex}>
|
||||
{color.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
<Menu.Divider />
|
||||
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
const menu = {
|
||||
items: [
|
||||
...(bodyshop.appt_colors &&
|
||||
bodyshop.appt_colors.map((color) => ({
|
||||
style: { color: color.color.hex },
|
||||
key: color.color.hex,
|
||||
label: color.label,
|
||||
}))),
|
||||
{ type: "divider" },
|
||||
{ key: "null", value: t("general.actions.clear") },
|
||||
],
|
||||
selectedKeys: [event.color],
|
||||
onClick: onClick,
|
||||
};
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu}>
|
||||
<Dropdown menu={menu}>
|
||||
<a href=" #" onClick={(e) => e.preventDefault()}>
|
||||
{selectedColor}
|
||||
<DownOutlined />
|
||||
|
||||
@@ -3,13 +3,10 @@ import {
|
||||
Button,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
Menu,
|
||||
notification,
|
||||
Form, notification,
|
||||
Popover,
|
||||
Select,
|
||||
Space,
|
||||
Space
|
||||
} from "antd";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import moment from "moment";
|
||||
@@ -21,7 +18,7 @@ import { Link, useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
openChatByPhone,
|
||||
setMessage,
|
||||
setMessage
|
||||
} from "../../redux/messaging/messaging.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
@@ -35,8 +32,6 @@ import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.
|
||||
import ScheduleAtChange from "./job-at-change.component";
|
||||
import ScheduleEventColor from "./schedule-event.color.component";
|
||||
import ScheduleEventNote from "./schedule-event.note.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -61,44 +56,16 @@ export function ScheduleEventComponent({
|
||||
const [visible, setVisible] = useState(false);
|
||||
const history = useHistory();
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
const [title, setTitle] = useState(event.title);
|
||||
const blockContent = (
|
||||
<Space direction="vertical" wrap>
|
||||
<Input
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.currentTarget.value)}
|
||||
onBlur={async () => {
|
||||
await updateAppointment({
|
||||
variables: {
|
||||
appid: event.id,
|
||||
app: {
|
||||
title: title,
|
||||
},
|
||||
},
|
||||
optimisticResponse: {
|
||||
update_appointments: {
|
||||
__typename: "appointments_mutation_response",
|
||||
returning: [
|
||||
{
|
||||
...event,
|
||||
title: title,
|
||||
__typename: "appointments",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
const blockContent = (
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => handleCancel({ id: event.id })}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("appointments.actions.unblock")}
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
|
||||
const popoverContent = (
|
||||
@@ -185,10 +152,12 @@ export function ScheduleEventComponent({
|
||||
) : null}
|
||||
{event.job ? (
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
menu={{
|
||||
items: [
|
||||
{
|
||||
disabled: event.arrived,
|
||||
label: t("general.labels.email"),
|
||||
onClick: () => {
|
||||
const Template = TemplateList("job").appointment_reminder;
|
||||
GenerateDocument(
|
||||
{
|
||||
@@ -202,13 +171,12 @@ export function ScheduleEventComponent({
|
||||
"e",
|
||||
event.job && event.job.id
|
||||
);
|
||||
}}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("general.labels.email")}
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t("general.labels.sms"),
|
||||
disabled: event.arrived || !bodyshop.messagingservicesid,
|
||||
onClick: () => {
|
||||
const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
|
||||
if (p && p.isValid()) {
|
||||
openChatByPhone({
|
||||
@@ -228,67 +196,54 @@ export function ScheduleEventComponent({
|
||||
message: t("messaging.error.invalidphone"),
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={event.arrived || !bodyshop.messagingservicesid}
|
||||
>
|
||||
{t("general.labels.sms")}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
<Button>{t("appointments.actions.sendreminder")}</Button>
|
||||
</Dropdown>
|
||||
) : null}
|
||||
{event.arrived ? (
|
||||
<Popover
|
||||
trigger="click"
|
||||
disabled={event.arrived}
|
||||
content={
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={({ lost_sale_reason }) => {
|
||||
handleCancel({ id: event.id, lost_sale_reason });
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="lost_sale_reason"
|
||||
label={t("jobs.fields.lost_sale_reason")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
|
||||
label: lsr,
|
||||
value: lsr,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button htmlType="submit">
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
</Form>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
// onClick={() => handleCancel(event.id)}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
) : (
|
||||
<Popover
|
||||
trigger="click"
|
||||
disabled={event.arrived}
|
||||
content={
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={({ lost_sale_reason }) => {
|
||||
handleCancel({ id: event.id, lost_sale_reason });
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
name="lost_sale_reason"
|
||||
label={t("jobs.fields.lost_sale_reason")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
|
||||
label: lsr,
|
||||
value: lsr,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Button htmlType="submit">
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
</Form>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
// onClick={() => handleCancel(event.id)}
|
||||
disabled={event.arrived}
|
||||
>
|
||||
{t("appointments.actions.cancel")}
|
||||
</Button>
|
||||
</Popover>
|
||||
)}
|
||||
|
||||
</Popover>
|
||||
{event.isintake ? (
|
||||
<Button
|
||||
disabled={event.arrived}
|
||||
@@ -369,8 +324,8 @@ export function ScheduleEventComponent({
|
||||
|
||||
return (
|
||||
<Popover
|
||||
visible={visible}
|
||||
onVisibleChange={(vis) => !event.vacation && setVisible(vis)}
|
||||
open={visible}
|
||||
onOpenChange={(vis) => !event.vacation && setVisible(vis)}
|
||||
trigger="click"
|
||||
content={event.block ? blockContent : popoverContent}
|
||||
style={{
|
||||
|
||||
@@ -2,16 +2,12 @@ import { useMutation } from "@apollo/client";
|
||||
import { notification } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import ScheduleEventComponent from "./schedule-event.component";
|
||||
|
||||
export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
@@ -38,24 +34,16 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
|
||||
const jobUpdate = await updateJob({
|
||||
variables: {
|
||||
jobId: event.job.id,
|
||||
|
||||
job: {
|
||||
date_scheduled: null,
|
||||
scheduled_in: null,
|
||||
scheduled_completion: null,
|
||||
lost_sale_reason,
|
||||
date_lost_sale: new Date(),
|
||||
status: bodyshop.md_ro_statuses.default_imported,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!jobUpdate.errors) {
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid: event.job.id,
|
||||
operation: AuditTrailMapping.appointmentcancel(lost_sale_reason),
|
||||
})
|
||||
);
|
||||
}
|
||||
if (!!jobUpdate.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.updating", {
|
||||
|
||||
@@ -87,8 +87,7 @@ export default function JobBillsTotalComponent({
|
||||
const totalPartsSublet = Dinero(totals.parts.parts.total)
|
||||
.add(Dinero(totals.parts.sublets.total))
|
||||
.add(Dinero(totals.additional.shipping))
|
||||
.add(Dinero(totals.additional.towing))
|
||||
.add(Dinero(totals.additional.additionalCosts));
|
||||
.add(Dinero(totals.additional.towing));
|
||||
|
||||
const discrepancy = totalPartsSublet.subtract(billTotals);
|
||||
|
||||
|
||||
@@ -1,52 +1,47 @@
|
||||
import { Button, notification } from "antd";
|
||||
import Axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
export default function JobCalculateTotals({ job, disabled, refetch }) {
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import Dinero from "dinero.js";
|
||||
export default function JobCalculateTotals({ job, disabled }) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
|
||||
const handleCalculate = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setLoading(true);
|
||||
const newTotals = (
|
||||
await Axios.post("/job/totals", {
|
||||
job: job,
|
||||
})
|
||||
).data;
|
||||
|
||||
await Axios.post("/job/totalsssu", {
|
||||
id: job.id,
|
||||
});
|
||||
|
||||
if (refetch) refetch();
|
||||
// const result = await updateJob({
|
||||
// refetchQueries: ["GET_JOB_BY_PK"],
|
||||
// awaitRefetchQueries: true,
|
||||
// variables: {
|
||||
// jobId: job.id,
|
||||
// job: {
|
||||
// job_totals: newTotals,
|
||||
// clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
|
||||
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
|
||||
// "0.00"
|
||||
// ),
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// if (!!!result.errors) {
|
||||
// notification["success"]({ message: t("jobs.successes.updated") });
|
||||
// } else {
|
||||
// notification["error"]({
|
||||
// message: t("jobs.errors.updating", {
|
||||
// error: JSON.stringify(result.errors),
|
||||
// }),
|
||||
// });
|
||||
// }
|
||||
} catch (error) {
|
||||
const result = await updateJob({
|
||||
refetchQueries: ["GET_JOB_BY_PK"],
|
||||
awaitRefetchQueries: true,
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {
|
||||
job_totals: newTotals,
|
||||
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
|
||||
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
|
||||
"0.00"
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({ message: t("jobs.successes.updated") });
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.updating", {
|
||||
error: JSON.stringify(error),
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Form, Input, notification, Switch } from "antd";
|
||||
import moment from "moment-business-days";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -13,15 +12,16 @@ import {
|
||||
MARK_LATEST_APPOINTMENT_ARRIVED,
|
||||
} from "../../../../graphql/appointments.queries";
|
||||
import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
|
||||
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
|
||||
import { insertAuditTrail } from "../../../../redux/application/application.actions";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
|
||||
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
|
||||
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
|
||||
import moment from "moment-business-days";
|
||||
import { insertAuditTrail } from "../../../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
|
||||
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -230,7 +230,6 @@ export function JobChecklistForm({
|
||||
)),
|
||||
scheduled_delivery:
|
||||
job.scheduled_delivery && moment(job.scheduled_delivery),
|
||||
production_vars: job.production_vars,
|
||||
}),
|
||||
...(type === "deliver" && {
|
||||
removeFromProduction: true,
|
||||
|
||||
@@ -39,7 +39,7 @@ export function JobCostingModalContainer({
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
open={visible}
|
||||
title={t("jobs.labels.jobcosting")}
|
||||
onOk={() => {
|
||||
toggleModalVisible();
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import Dinero from "dinero.js";
|
||||
import {pageLimit} from "../../utils/config";
|
||||
export default function JobCostingPartsTable({ data, summaryData }) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [state, setState] = useState({
|
||||
@@ -99,7 +98,7 @@ export default function JobCostingPartsTable({ data, summaryData }) {
|
||||
x: "50%", //y: "40rem"
|
||||
}}
|
||||
onChange={handleTableChange}
|
||||
pagination={{ position: "top", defaultPageSize: pageLimit }}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={filteredData}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user