Compare commits

..

1 Commits

Author SHA1 Message Date
Dave Richer
ee8cbe33c4 Pagination Fix 2023-12-04 14:28:44 -05:00
346 changed files with 5987 additions and 76853 deletions

View File

@@ -42,156 +42,32 @@ jobs:
app-build:
docker:
- image: cimg/node:16.15.0
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: npm i
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: npm run build
- run: yarn run build
- aws-s3/sync:
from: build
to: "s3://imex-online-production/"
arguments: "--exclude '*.map'"
- jira/notify
rome-api-deploy:
docker:
- image: "cimg/base:stable"
steps:
- checkout
- eb/setup
- run:
command: |
eb init romeonline-productionapi -r us-east-2 -p "Node.js 18 running on 64bit Amazon Linux 2"
eb status --verbose
eb deploy
eb status
- jira/notify
rome-hasura-migrate:
docker:
- image: cimg/node:16.15.0
parameters:
secret:
type: string
default: $HASURA_PROD_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
rome-app-build:
docker:
- image: cimg/node:16.15.0
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build
- aws-s3/sync:
from: build
to: "s3://rome-online-production/"
- jira/notify
test-rome-hasura-migrate:
docker:
- image: cimg/node:16.15.0
parameters:
secret:
type: string
default: $HASURA_ROME_TEST_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
echo ${HASURA_TEST_SECRET}
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
test-rome-app-build:
docker:
- image: cimg/node:16.15.0
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:test
- aws-s3/sync:
from: build
to: "s3://rome-online-test/"
- jira/notify
app-beta-build:
docker:
- image: cimg/node:18.18.2
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build
- aws-s3/sync:
from: build
to: "s3://imex-online-beta/"
- jira/notify
rome-app-beta-build:
docker:
- image: cimg/node:18.18.2
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build
- aws-s3/sync:
from: build
to: "s3://rome-online-production-beta/"
- jira/notify
test-hasura-migrate:
docker:
@@ -216,67 +92,33 @@ jobs:
test-app-build:
docker:
- image: cimg/node:16.15.0
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: npm i
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: npm run build:test
- run: yarn run build:test
- aws-s3/sync:
from: build
to: "s3://imex-online-test/"
arguments: "--exclude '*.map'"
- jira/notify
test-app-beta-build:
docker:
- image: cimg/node:18.18.2
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:test
- aws-s3/sync:
from: build
to: "s3://imex-online-test-beta/"
- jira/notify
rome-test-app-beta-build:
docker:
- image: cimg/node:18.18.2
resource_class: large
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- run:
name: Install Dependencies
command: npm i
- run: npm run build:test
- aws-s3/sync:
from: build
to: "s3://rome-online-test-beta/"
- jira/notify
admin-app-build:
docker:
- image: cimg/node:16.15.0
@@ -318,59 +160,21 @@ workflows:
filters:
branches:
only: master
- app-beta-build:
filters:
branches:
only: master-beta
- rome-app-beta-build:
filters:
branches:
only: rome/master-beta
- hasura-migrate:
secret: ${HASURA_PROD_SECRET}
filters:
branches:
only: master
- rome-api-deploy:
filters:
branches:
only: rome/master
- rome-app-build:
filters:
branches:
only: rome/master
- rome-hasura-migrate:
secret: ${HASURA_PROD_SECRET}
filters:
branches:
only: rome/master
- test-app-build:
filters:
branches:
only: test
- rome-test-app-beta-build:
filters:
branches:
only: rome/test-beta
- test-app-beta-build:
filters:
branches:
only: test-beta
- test-hasura-migrate:
secret: ${HASURA_TEST_SECRET}
filters:
branches:
only: test
- test-rome-app-build:
filters:
branches:
only: rome/test
- test-rome-hasura-migrate:
secret: ${HASURA_ROME_TEST_SECRET}
filters:
branches:
only: rome/test
#- admin-app-build:
#filters:
#branches:
#only: master
#only: master

View File

@@ -1,16 +0,0 @@
exports.default = {
printWidth: 120,
useTabs: false,
tabWidth: 2,
trailingComma: "es5",
semi: true,
singleQuote: false,
bracketSpacing: true,
arrowParens: "always",
jsxSingleQuote: false,
bracketSameLine: false,
endOfLine: "lf",
importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
};

View File

@@ -1,184 +0,0 @@
# Filters and Sorters
This documentation details the schema required for `.filters` files on the report server. It is used to dynamically
modify the graphQL query and provide the user more power over their reports.
For filters and sorters, valid types include (`type` key in the schema):
- string (default)
- number
- bool or boolean
- date
## Special Notes
- When passing the data to the template server, the property filters and sorters is added to the data object and will reflect the filters and sorters the user has selected
## High level Schema Overview
```javascript
const schema = {
"filters": [
{
"name": "jobs.joblines.mod_lb_hrs", // Name and path of the field in the graphQL query
"translation": "jobs.joblines.mod_lb_hrs_1", // Translation key for the label used in the GUI
"label": "mod_lb_hrs_1", // Label used in the case the GUI does not contain a translation
"type": "number" // Type of field, can be number or string currently
},
// ... more filters
],
"sorters": [
{
"name": "jobs.joblines.mod_lb_hrs", // Name and path of the field in the graphQL query
"translation": "jobs.joblines.mod_lb_hrs_1", // Translation key for the label used in the GUI
"label": "mod_lb_hrs_1", // Label used in the case the GUI does not contain a translation
"type": "number" // Type of field, can be number or string currently
},
// ... more sorters
],
"dates": {
// This is not yet implemented and will be added in a future release
}
}
```
## Filters
Filters effect the where clause of the graphQL query. They are used to filter the data returned from the server.
A note on special notation used in the `name` field.
## Reflection
Filters can make use of reflection to pre-fill select boxes, the following is an example of that in the filters file.
```json
{
"name": "jobs.status",
"translation": "jobs.fields.status",
"label": "Status",
"type": "string",
"reflector": {
"type": "internal",
"name": "special.job_statuses"
}
},
```
in this example, a reflector with the type 'internal' (all types at the moment require this, and it is used for future functionality), with a name of `special.job_statuses`
The following cases are available
- `special.job_statuses` - This will reflect the statuses of the jobs table `bodyshop.md_ro_statuses.statuses'`
- `special.cost_centers` - This will reflect the cost centers `bodyshop.md_responsibility_centers.costs`
- `special.categories` - This will reflect the categories `bodyshop.md_categories`
- `special.insurance_companies` - This will reflect the insurance companies `bodyshop.md_ins_cos`'
- `special.employee_teams` - This will reflect the employee teams `bodyshop.employee_teams`
- `special.employees` - This will reflect the employees `bodyshop.employees`
- `special.first_names` - This will reflect the first names `bodyshop.employees`
- `special.last_names` - This will reflect the last names `bodyshop.employees`
- `special.referral_sources` - This will reflect the referral sources `bodyshop.md_referral_sources
- `special.class`- This will reflect the class `bodyshop.md_classes`
-
### Path without brackets, multi level
`"name": "jobs.joblines.mod_lb_hrs",`
This will produce a where clause at the `joblines` level of the graphQL query,
```graphql
query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!) {
jobs(
where: {date_invoiced: {_is_null: true}, date_open: {_gte: $starttz, _lte: $endtz}, ro_number: {_is_null: false}, voided: {_eq: false}}
) {
joblines(
order_by: {line_no: asc}
where: {removed: {_eq: false}, mod_lb_hrs: {_lt: 3}}
) {
line_no
mod_lbr_ty
mod_lb_hrs
convertedtolbr
convertedtolbr_data
}
ownr_co_nm
ownr_fn
ownr_ln
plate_no
ro_number
status
v_make_desc
v_model_desc
v_model_yr
v_vin
v_color
}
}
```
### Path with brackets,top level
`"name": "[jobs].joblines.mod_lb_hrs",`
This will produce a where clause at the `jobs` level of the graphQL query.
```graphql
query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!) {
jobs(
where: {date_invoiced: {_is_null: true}, date_open: {_gte: $starttz, _lte: $endtz}, ro_number: {_is_null: false}, voided: {_eq: false}, joblines: {mod_lb_hrs: {_gt: 4}}}
) {
joblines(
order_by: {line_no: asc}
where: {removed: {_eq: false}}
) {
line_no
mod_lbr_ty
mod_lb_hrs
convertedtolbr
convertedtolbr_data
}
ownr_co_nm
ownr_fn
ownr_ln
plate_no
ro_number
status
v_make_desc
v_model_desc
v_model_yr
v_vin
v_color
}
}
```
## Known Caveats
- Will only support two level of nesting in the graphQL query `jobs.joblines.mod_lb_hrs` vs `[jobs].joblines.mod_lb_hrs`
is fine, but `jobs.[joblines.].some_table.mod_lb_hrs` is not.
- The type object must be 'string' or 'number' or 'bool' or 'boolean' or 'date' and is case-sensitive.
- The `translation` key is used to look up the label in the GUI, if it is not found, the `label` key is used.
- Do not add the ability to filter things that are already filtered as part of the original query, this would be
redundant and could cause issues.
- Do not add the ability to filter on things like FK constraints, must like the above example.
## Sorters
- Sorters follow the same schema as filters, however, they do not do square bracket wrapping to indicate level hoisting,
a filter added on `job.md_status` would be added at the top level, and a filter added on `jobs.joblines.mod_lb_hrs`
would be added at the `joblines` level.
- Most of the reports currently do sorting on a template level, this will need to change to actually see the results
using the sorters.
### Default Sorters
- A sorter can be given a default object containing a `order` and `direction` key value. This will be used to sort the report if the user does not select any of the sorters themselves.
- The `order` key is the order in which the sorters are applied, and the `direction` key is the direction of the sort, either `asc` or `desc`.
```json
{
"name": "jobs.joblines.mod_lb_hrs",
"translation": "jobs.joblines.mod_lb_hrs_1",
"label": "mod_lb_hrs_1",
"type": "number",
"default": {
"order": 1,
"direction": "asc"
}
}
```

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -1,13 +1,14 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.romeonline.io/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.romeonline.io/v1/graphql
GENERATE_SOURCEMAP=false
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_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

View File

@@ -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

3
client/.gitignore vendored
View File

@@ -1,3 +0,0 @@
# Sentry Config File
.sentryclirc

View File

@@ -1,25 +1,25 @@
// craco.config.js
const TerserPlugin = require("terser-webpack-plugin");
const CracoLessPlugin = require("craco-less");
//const SentryWebpackPlugin = require("@sentry/webpack-plugin");
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
module.exports = {
plugins: [
// {
// plugin: SentryWebpackPlugin,
// options: {
// // sentry-cli configuration
// authToken:
// "6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
// org: "snapt-software",
// project: "imexonline",
// release: process.env.REACT_APP_GIT_SHA,
{
plugin: SentryWebpackPlugin,
options: {
// sentry-cli configuration
authToken:
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
org: "snapt-software",
project: "imexonline",
release: process.env.REACT_APP_GIT_SHA,
// // webpack-specific configuration
// include: ".",
// ignore: ["node_modules", "webpack.config.js"],
// },
// },
// webpack-specific configuration
include: ".",
ignore: ["node_modules", "webpack.config.js"],
},
},
{
plugin: CracoLessPlugin,
options: {
@@ -27,7 +27,7 @@ module.exports = {
lessOptions: {
modifyVars: {
...(process.env.NODE_ENV === "development"
? { "@primary-color": "#B22234" }
? { "@primary-color": "#a51d1d" }
: {
//"@primary-color": "#1DA57A"
}),

647
client/package-lock.json generated
View File

@@ -13,14 +13,12 @@
"@craco/craco": "^7.0.0",
"@fingerprintjs/fingerprintjs": "^3.4.2",
"@jsreport/browser-client": "^3.1.0",
"@sentry/cli": "^2.27.0",
"@sentry/react": "^7.99.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",
"apollo-link-sentry": "^3.3.0",
"axios": "^1.3.4",
"craco-less": "^2.0.0",
"dinero.js": "^1.9.1",
@@ -4216,195 +4214,40 @@
"integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==",
"license": "MIT"
},
"node_modules/@sentry-internal/feedback": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.99.0.tgz",
"integrity": "sha512-exIO1o+bE0MW4z30FxC0cYzJ4ZHSMlDPMHCBDPzU+MWGQc/fb8s58QUrx5Dnm6HTh9G3H+YlroCxIo9u0GSwGQ==",
"dependencies": {
"@sentry/core": "7.99.0",
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry-internal/feedback/node_modules/@sentry/core": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
"dependencies": {
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry-internal/feedback/node_modules/@sentry/types": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry-internal/feedback/node_modules/@sentry/utils": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
"dependencies": {
"@sentry/types": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry-internal/replay-canvas": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.99.0.tgz",
"integrity": "sha512-PoIkfusToDq0snfl2M6HJx/1KJYtXxYhQplrn11kYadO04SdG0XGXf4h7wBTMEQ7LDEAtQyvsOu4nEQtTO3YjQ==",
"dependencies": {
"@sentry/core": "7.99.0",
"@sentry/replay": "7.99.0",
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/core": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
"dependencies": {
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/types": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/utils": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
"dependencies": {
"@sentry/types": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry-internal/tracing": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.99.0.tgz",
"integrity": "sha512-z3JQhHjoM1KdM20qrHwRClKJrNLr2CcKtCluq7xevLtXHJWNAQQbafnWD+Aoj85EWXBzKt9yJMv2ltcXJ+at+w==",
"dependencies": {
"@sentry/core": "7.99.0",
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry-internal/tracing/node_modules/@sentry/core": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
"dependencies": {
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry-internal/tracing/node_modules/@sentry/types": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry-internal/tracing/node_modules/@sentry/utils": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
"dependencies": {
"@sentry/types": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.99.0.tgz",
"integrity": "sha512-bgfoUv3wkwwLgN5YUOe0ibB3y268ZCnamZh6nLFqnY/UBKC1+FXWFdvzVON/XKUm62LF8wlpCybOf08ebNj2yg==",
"version": "7.40.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.40.0.tgz",
"integrity": "sha512-07rZ+cTcpmYB1r84/oZtmSPJJvLCxW8yIh/5s4MdKRyZpqIDKhOz6cCS/4j+l1V+MeLcNLZBjFtNdKA2eocTpg==",
"license": "MIT",
"dependencies": {
"@sentry-internal/feedback": "7.99.0",
"@sentry-internal/replay-canvas": "7.99.0",
"@sentry-internal/tracing": "7.99.0",
"@sentry/core": "7.99.0",
"@sentry/replay": "7.99.0",
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
"@sentry/core": "7.40.0",
"@sentry/replay": "7.40.0",
"@sentry/types": "7.40.0",
"@sentry/utils": "7.40.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser/node_modules/@sentry/core": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
"dependencies": {
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser/node_modules/@sentry/types": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser/node_modules/@sentry/utils": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
"dependencies": {
"@sentry/types": "7.99.0"
},
"engines": {
"node": ">=8"
}
"node_modules/@sentry/browser/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD"
},
"node_modules/@sentry/cli": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.27.0.tgz",
"integrity": "sha512-pc0opd71W8lGhYvmB1keQtJkarxzCS9f9ErKYv6TfXOOX6drvwkyA6vD/6xEnpzyvqGAuGRU4T4sEeLD3irwUQ==",
"version": "1.74.6",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.74.6.tgz",
"integrity": "sha512-pJ7JJgozyjKZSTjOGi86chIngZMLUlYt2HOog+OJn+WGvqEkVymu8m462j1DiXAnex9NspB4zLLNuZ/R6rTQHg==",
"dev": true,
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"https-proxy-agent": "^5.0.0",
"mkdirp": "^0.5.5",
"node-fetch": "^2.6.7",
"npmlog": "^4.1.2",
"progress": "^2.0.3",
"proxy-from-env": "^1.1.0",
"which": "^2.0.2"
@@ -4413,124 +4256,7 @@
"sentry-cli": "bin/sentry-cli"
},
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@sentry/cli-darwin": "2.27.0",
"@sentry/cli-linux-arm": "2.27.0",
"@sentry/cli-linux-arm64": "2.27.0",
"@sentry/cli-linux-i686": "2.27.0",
"@sentry/cli-linux-x64": "2.27.0",
"@sentry/cli-win32-i686": "2.27.0",
"@sentry/cli-win32-x64": "2.27.0"
}
},
"node_modules/@sentry/cli-darwin": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.27.0.tgz",
"integrity": "sha512-/DOZlN5rK19g7YP2OaVNauQhUrRfJ88RDr6qURFiqdxYHDc3isPFGHZJmeZBTwOnDDepyZb4XLaOyfwvAOxHig==",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-arm": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.27.0.tgz",
"integrity": "sha512-JmMQ9zgFhkZUEN5WIYuJisu4Jif/ThRHDjbsbXBRbUkkgRn88hgUfg299djMvlZZxjpl3K9AEua+1TIUeQd0Sg==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux",
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-arm64": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.27.0.tgz",
"integrity": "sha512-f+zuB9XGfB8pNamNgSDhqsavuLuzi6saZxbr3uQf30bA5AESI5hspOd1zPcidOORCVZxiPzQe3+T7avBI1XLuw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux",
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-i686": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.27.0.tgz",
"integrity": "sha512-/4eyz7jnYp20mZqNtpvCEBkxFW0nEjEZRo2BiASQ5/7K8CmoJRe1vhpDA0WOfzi1zTFIfpdE1/RZm2CjHS6DHQ==",
"cpu": [
"x86",
"ia32"
],
"optional": true,
"os": [
"linux",
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-x64": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.27.0.tgz",
"integrity": "sha512-ptu7wXecnYssihzHlxEOaqbFHWmNEfbepBKGXTdWK2kC+D51+7yHsR9xRdThwVID1bisFgjAveKmBQjmKuXjHQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux",
"freebsd"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-win32-i686": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.27.0.tgz",
"integrity": "sha512-Db4/xmdE5qV4Aq7Yc8vRw22Y46JJdGMdsMsl5jIf0GVSQPgO23O/2uTiDGpPOdeq91K9EtvpH1zQfDLIfLMaXw==",
"cpu": [
"x86",
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-win32-x64": {
"version": "2.27.0",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.27.0.tgz",
"integrity": "sha512-q7y/BH4iGfs0TD5PXh2Q8oqnTbOIufoT1NWJcKqvZcOiqCLK3PNUiq7xUeX1PMTrFYAh3Bm6EekOnMavqvbGmg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
"node": ">= 8"
}
},
"node_modules/@sentry/core": {
@@ -4554,15 +4280,16 @@
"license": "0BSD"
},
"node_modules/@sentry/react": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.99.0.tgz",
"integrity": "sha512-RtHwgzMHJhzJfSQpVG0SDPQYMTGDX3Q37/YWI59S4ALMbSW4/F6n/eQAvGVYZKbh2UCSqgFuRWaXOYkSZT17wA==",
"version": "7.40.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.40.0.tgz",
"integrity": "sha512-7yYagpOCdsXnVTtLL8Y7wAf2xXgsk2ncuju3O/G4kEckkLewZWmQeoknOSGFlAgVdGNhTaXc2WGzgOiBMOkhug==",
"license": "MIT",
"dependencies": {
"@sentry/browser": "7.99.0",
"@sentry/core": "7.99.0",
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0",
"hoist-non-react-statics": "^3.3.2"
"@sentry/browser": "7.40.0",
"@sentry/types": "7.40.0",
"@sentry/utils": "7.40.0",
"hoist-non-react-statics": "^3.3.2",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
@@ -4571,82 +4298,26 @@
"react": "15.x || 16.x || 17.x || 18.x"
}
},
"node_modules/@sentry/react/node_modules/@sentry/core": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
"dependencies": {
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/react/node_modules/@sentry/types": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/react/node_modules/@sentry/utils": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
"dependencies": {
"@sentry/types": "7.99.0"
},
"engines": {
"node": ">=8"
}
"node_modules/@sentry/react/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD"
},
"node_modules/@sentry/replay": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.99.0.tgz",
"integrity": "sha512-gyN/I2WpQrLAZDT+rScB/0jnFL2knEVBo8U8/OVt8gNP20Pq8T/rDZKO/TG0cBfvULDUbJj2P4CJryn2p/O2rA==",
"version": "7.40.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.40.0.tgz",
"integrity": "sha512-Y9Kvo9jKouUdrHQhHVv5SmWZClF5o7BFI6oVpLlv4zXORPQlyoZONM/9sxiMvvH73alDSpxzCoxyhlypAOH4ww==",
"license": "MIT",
"dependencies": {
"@sentry-internal/tracing": "7.99.0",
"@sentry/core": "7.99.0",
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
"@sentry/core": "7.40.0",
"@sentry/types": "7.40.0",
"@sentry/utils": "7.40.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry/replay/node_modules/@sentry/core": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.99.0.tgz",
"integrity": "sha512-vOAtzcAXEUtS/oW7wi3wMkZ3hsb5Ch96gKyrrj/mXdOp2zrcwdNV6N9/pawq2E9P/7Pw8AXw4CeDZztZrjQLuA==",
"dependencies": {
"@sentry/types": "7.99.0",
"@sentry/utils": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/replay/node_modules/@sentry/types": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.99.0.tgz",
"integrity": "sha512-94qwOw4w40sAs5mCmzcGyj8ZUu/KhnWnuMZARRq96k+SjRW/tHFAOlIdnFSrt3BLPvSOK7R3bVAskZQ0N4FTmA==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/replay/node_modules/@sentry/utils": {
"version": "7.99.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.99.0.tgz",
"integrity": "sha512-cYZy5WNTkWs5GgggGnjfGqC44CWir0pAv4GVVSx0fsup4D4pMKBJPrtub15f9uC+QkUf3vVkqwpBqeFxtmJQTQ==",
"dependencies": {
"@sentry/types": "7.99.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/tracing": {
"version": "7.40.0",
"resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.40.0.tgz",
@@ -4710,27 +4381,6 @@
"node": ">= 8"
}
},
"node_modules/@sentry/webpack-plugin/node_modules/@sentry/cli": {
"version": "1.77.3",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.77.3.tgz",
"integrity": "sha512-c3eDqcDRmy4TFz2bFU5Y6QatlpoBPPa8cxBooaS4aMQpnIdLYPF1xhyyiW0LQlDUNc3rRjNF7oN5qKoaRoMTQQ==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"https-proxy-agent": "^5.0.0",
"mkdirp": "^0.5.5",
"node-fetch": "^2.6.7",
"progress": "^2.0.3",
"proxy-from-env": "^1.1.0",
"which": "^2.0.2"
},
"bin": {
"sentry-cli": "bin/sentry-cli"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.24.51",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
@@ -6462,21 +6112,12 @@
"@apollo/client": "^3.0.0"
}
},
"node_modules/apollo-link-sentry": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/apollo-link-sentry/-/apollo-link-sentry-3.3.0.tgz",
"integrity": "sha512-wLffWmo5sRw3rHN1Ck6azM0oxObvtaBBf3AC8cLX4SxhyjmkRIagGDji6CFkyAhxupPz0b9/H1u4Ocx+63lNug==",
"dependencies": {
"deepmerge": "^4.2.2",
"dot-prop": "^6.0.0",
"tslib": "^2.0.3",
"zen-observable-ts": "^1.2.5"
},
"peerDependencies": {
"@apollo/client": "^3.2.3",
"@sentry/browser": "^7.41.0",
"graphql": "15 - 16"
}
"node_modules/aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true,
"license": "ISC"
},
"node_modules/arch": {
"version": "2.2.0",
@@ -6499,6 +6140,17 @@
],
"license": "MIT"
},
"node_modules/are-we-there-yet": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
"integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
"dev": true,
"license": "ISC",
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
}
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
@@ -7919,6 +7571,16 @@
"node": ">=4"
}
},
"node_modules/code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/collect-v8-coverage": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz",
@@ -8081,6 +7743,13 @@
"node": ">=0.8"
}
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"dev": true,
"license": "ISC"
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -9242,6 +8911,13 @@
"node": ">=0.4.0"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"dev": true,
"license": "MIT"
},
"node_modules/denque": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
@@ -9542,28 +9218,6 @@
"tslib": "^2.0.3"
}
},
"node_modules/dot-prop": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz",
"integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==",
"dependencies": {
"is-obj": "^2.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/dot-prop/node_modules/is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
"engines": {
"node": ">=8"
}
},
"node_modules/dotenv": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz",
@@ -10810,8 +10464,6 @@
},
"node_modules/eventemitter2": {
"version": "6.4.7",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz",
"integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==",
"dev": true,
"license": "MIT"
},
@@ -11631,6 +11283,74 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
"dev": true,
"license": "ISC",
"dependencies": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.0",
"object-assign": "^4.1.0",
"signal-exit": "^3.0.0",
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wide-align": "^1.1.0"
}
},
"node_modules/gauge/node_modules/ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/gauge/node_modules/is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
"dev": true,
"license": "MIT",
"dependencies": {
"number-is-nan": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/gauge/node_modules/string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
"dev": true,
"license": "MIT",
"dependencies": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/gauge/node_modules/strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -12033,6 +11753,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
"dev": true,
"license": "ISC"
},
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
@@ -16017,6 +15744,19 @@
"node": ">=8"
}
},
"node_modules/npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
"license": "ISC",
"dependencies": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
"gauge": "~2.7.3",
"set-blocking": "~2.0.0"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -16029,6 +15769,16 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/nwsapi": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz",
@@ -18044,6 +17794,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
@@ -20775,6 +20526,13 @@
"node": ">= 0.8.0"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"dev": true,
"license": "ISC"
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@@ -22352,6 +22110,19 @@
"is-typedarray": "^1.0.0"
}
},
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/typescript-compare": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz",
@@ -23309,6 +23080,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/wildcard": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",

View File

@@ -9,14 +9,12 @@
"@craco/craco": "^7.0.0",
"@fingerprintjs/fingerprintjs": "^3.4.2",
"@jsreport/browser-client": "^3.1.0",
"@sentry/cli": "^2.27.0",
"@sentry/react": "^7.99.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",
"apollo-link-sentry": "^3.3.0",
"axios": "^1.3.4",
"craco-less": "^2.0.0",
"dinero.js": "^1.9.1",
@@ -90,14 +88,13 @@
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "craco start",
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build && npm run sentry:sourcemaps",
"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!'",
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"test": "cypress open",
"eject": "react-scripts eject",
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org imex --project imexonline ./build && sentry-cli sourcemaps upload --org imex --project imexonline ./build"
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular ."
},
"eslintConfig": {
"extends": [

View File

@@ -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 = {

View File

@@ -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>

View File

@@ -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

View File

@@ -8,7 +8,6 @@ import { useTranslation } from "react-i18next";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import client from "../utils/GraphQLClient";
import App from "./App";
import * as Sentry from "@sentry/react";
moment.locale("en-US");
@@ -19,7 +18,7 @@ export const factory = SplitSdk({
},
});
function AppContainer() {
export default function AppContainer() {
const { t } = useTranslation();
return (
@@ -28,12 +27,6 @@ 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
@@ -49,5 +42,3 @@ function AppContainer() {
</ApolloProvider>
);
}
export default Sentry.withProfiler(AppContainer);

View File

@@ -22,7 +22,6 @@ import {
} from "../redux/user/user.selectors";
import PrivateRoute from "../utils/private-route";
import "./App.styles.scss";
import handleBeta from "../utils/handleBeta";
const ResetPassword = lazy(() =>
import("../pages/reset-password/reset-password.component")
@@ -58,6 +57,7 @@ export function App({
if (!navigator.onLine) {
setOnline(false);
}
checkUserSession();
}, [checkUserSession, setOnline]);
@@ -73,14 +73,13 @@ export function App({
window.addEventListener("online", function (e) {
setOnline(true);
});
useEffect(() => {
if (currentUser.authorized && bodyshop) {
client.setAttribute("imexshopid", bodyshop.imexshopid);
LogRocket.init("rome-online/rome-online");
if (client.getTreatment("LogRocket_Tracking") === "on") {
LogRocket.init("rome-online/rome-online");
console.log("LR Start");
LogRocket.init("gvfvfw/bodyshopapp");
}
}
}, [bodyshop, client, currentUser.authorized]);
@@ -108,11 +107,9 @@ export function App({
/>
);
handleBeta();
return (
<Switch>
<Suspense fallback={<LoadingSpinner />}>
<Suspense fallback={<LoadingSpinner message="ImEX Online" />}>
<ErrorBoundary>
<Route exact path="/" component={LandingPage} />
</ErrorBoundary>

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

View File

@@ -3,22 +3,10 @@ import { useMutation } from "@apollo/client";
import { Button, notification, Popconfirm } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { DELETE_BILL } from "../../graphql/bills.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(mapStateToProps, mapDispatchToProps)(BillDeleteButton);
export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) {
export default function BillDeleteButton({ bill, callback }) {
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const [deleteBill] = useMutation(DELETE_BILL);
@@ -48,10 +36,6 @@ export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) {
if (!!!result.errors) {
notification["success"]({ message: t("bills.successes.deleted") });
insertAuditTrail({
jobid: jobid,
operation: AuditTrailMapping.billdeleted(bill.invoice_number),
});
if (callback && typeof callback === "function") callback(bill.id);
} else {

View File

@@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE,
UPDATE_BILL_LINE
} from "../../graphql/bill-lines.queries";
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
@@ -20,7 +20,6 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
import AlertComponent from "../alert/alert.component";
import BillFormContainer from "../bill-form/bill-form.container";
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
import BillPrintButton from "../bill-print-button/bill-print-button.component";
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
@@ -177,7 +176,7 @@ export function BillDetailEditcontainer({
extra={
<Space>
<BillDetailEditReturn data={data} />
<BillPrintButton billid={search.billid} />
<Popconfirm
visible={visible}
onConfirm={() => form.submit()}

View File

@@ -1,5 +1,4 @@
import { useApolloClient, useMutation } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Button, Checkbox, Form, Modal, Space, notification } from "antd";
import _ from "lodash";
import React, { useEffect, useMemo, useState } from "react";
@@ -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:
@@ -105,12 +94,10 @@ function BillEnterModalContainer({
location,
outstanding_returns,
inventory,
federal_tax_exempt,
...remainingValues
} = values;
let adjustmentsToInsert = {};
let payrollAdjustmentsToInsert = [];
const r1 = await insertBill({
variables: {
@@ -126,33 +113,14 @@ 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,
@@ -178,20 +146,6 @@ function BillEnterModalContainer({
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
});
await Promise.all(
payrollAdjustmentsToInsert.map((li) => {
return updateJobLines({
variables: {
lineId: li.id,
line: {
convertedtolbr: li.convertedtolbr,
convertedtolbr_data: li.convertedtolbr_data,
},
},
});
})
);
const adjKeys = Object.keys(adjustmentsToInsert);
if (adjKeys.length > 0) {
//Query the adjustments, merge, and update them.
@@ -300,14 +254,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,
}
: {}),
},
},
});
@@ -376,12 +322,12 @@ function BillEnterModalContainer({
});
if (enterAgain) {
// form.resetFields();
form.resetFields();
form.resetFields();
form.setFieldsValue({
...formValues,
billlines: [],
});
form.resetFields();
} else {
toggleModalVisible();
}

View File

@@ -79,20 +79,6 @@ export function BillFormComponent({
});
};
// const handleFederalTaxExemptSwitchToggle = (checked) => {
// // Early gate
// if (!checked) return;
// const values = form.getFieldsValue("billlines");
// // Gate bill lines
// if (!values?.billlines?.length) return;
// const billlines = values.billlines.map((b) => {
// b.applicable_taxes.federal = false;
// return b;
// });
// form.setFieldsValue({ billlines });
// };
useEffect(() => {
if (job) form.validateFields(["is_credit_memo"]);
}, [job, form]);
@@ -380,15 +366,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")}
@@ -396,28 +380,14 @@ 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>
}
{
//Removed as a part of the merge to Rome Online. Federal tax not applicable.
// bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
// <Form.Item
// span={2}
// label={t("bills.labels.federal_tax_exempt")}
// name="federal_tax_exempt"
// >
// <Switch onChange={handleFederalTaxExemptSwitchToggle} />
// </Form.Item>
// ) : null
}
<Form.Item shouldUpdate span={13}>
<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([
"billlines",
@@ -435,32 +405,28 @@ export function BillFormComponent({
totals = CalculateBillTotal(values);
if (!!totals)
return (
<div align="right">
<div>
<Space wrap>
<Statistic
title={t("bills.labels.subtotal")}
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()}

View File

@@ -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)

View File

@@ -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));
}
});

View File

@@ -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)}`

View File

@@ -1,38 +0,0 @@
import { Button, Space } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
export default function BillPrintButton({ billid }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const Templates = TemplateList("job_special");
const submitHandler = async () => {
setLoading(true);
try {
await GenerateDocument(
{
name: Templates.parts_invoice_label_single.key,
variables: {
id: billid,
},
},
{},
"p"
);
} catch (e) {
console.warn("Warning: Error generating a document.");
}
setLoading(false);
};
return (
<Space wrap>
<Button loading={loading} onClick={submitHandler}>
{t("bills.labels.printlabels")}
</Button>
</Space>
);
}

View File

@@ -9,8 +9,8 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
@@ -58,7 +58,7 @@ export function BillsListTableComponent({
<EditFilled />
</Button>
)}
<BillDeleteButton bill={record} jobid={job.id} />
<BillDeleteButton bill={record} />
<BillDetailEditReturnComponent
data={{ bills_by_pk: { ...record, jobid: job.id } }}
disabled={

View File

@@ -4,7 +4,6 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
import ChatLabelComponent from "../chat-label/chat-label.component";
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
export default function ChatConversationTitle({ conversation }) {
@@ -14,7 +13,6 @@ export default function ChatConversationTitle({ conversation }) {
{conversation && conversation.phone_num}
</PhoneNumberFormatter>
<ChatLabelComponent conversation={conversation} />
<ChatPrintButton conversation={conversation} />
<ChatConversationTitleTags
jobConversations={
(conversation && conversation.job_conversations) || []

View File

@@ -1,59 +0,0 @@
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
import { Space, Spin } from "antd";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
export function ChatPrintButton({ conversation }) {
const [loading, setLoading] = useState(false);
return (
<Space wrap>
<PrinterOutlined
onClick={() => {
setLoading(true);
GenerateDocument(
{
name: TemplateList("messaging").conversation_list.key,
variables: { id: conversation.id },
},
{
subject: TemplateList("messaging").conversation_list.subject,
},
"p",
conversation.id
);
setLoading(false);
}}
/>
<MailOutlined
onClick={() => {
setLoading(true);
GenerateDocument(
{
name: TemplateList("messaging").conversation_list.key,
variables: { id: conversation.id },
},
{
subject: TemplateList("messaging").conversation_list.subject,
},
"e",
conversation.id
);
setLoading(false);
}}
/>
{loading && <Spin />}
</Space>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatPrintButton);

View File

@@ -35,15 +35,6 @@ export default function ContractsCarsComponent({
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => <div>{t(record.status)}</div>,
},
{
title: t("courtesycars.fields.readiness"),
dataIndex: "readiness",
key: "readiness",
sorter: (a, b) => alphaSort(a.readiness, b.readiness),
sortOrder:
state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order,
render: (text, record) => t(record.readiness),
},
{
title: t("courtesycars.fields.year"),
dataIndex: "year",

View File

@@ -68,30 +68,6 @@ export default function ContractFormComponent({
<FormDateTimePicker />
</Form.Item>
)}
{create && (
<Form.Item
shouldUpdate={(p, c) => p.scheduledreturn !== c.scheduledreturn}
>
{() => {
const insuranceOver =
selectedCar &&
selectedCar.insuranceexpires &&
moment(selectedCar.insuranceexpires)
.endOf("day")
.isBefore(moment(form.getFieldValue("scheduledreturn")));
if (insuranceOver)
return (
<Space direction="vertical" style={{ color: "tomato" }}>
<span>
<WarningFilled style={{ marginRight: ".3rem" }} />
{t("contracts.labels.insuranceexpired")}
</span>
</Space>
);
return <></>;
}}
</Form.Item>
)}
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item
@@ -114,17 +90,16 @@ export default function ContractFormComponent({
>
{() => {
const mileageOver =
selectedCar && selectedCar.nextservicekm
? selectedCar.nextservicekm <= form.getFieldValue("kmstart")
: false;
selectedCar &&
selectedCar.nextservicekm <= form.getFieldValue("kmstart");
const dueForService =
selectedCar &&
selectedCar.nextservicedate &&
moment(selectedCar.nextservicedate)
.endOf("day")
.isSameOrBefore(
moment(form.getFieldValue("scheduledreturn"))
);
moment(selectedCar.nextservicedate).isBefore(
moment(form.getFieldValue("scheduledreturn"))
);
if (mileageOver || dueForService)
return (
<Space direction="vertical" style={{ color: "tomato" }}>
@@ -142,6 +117,7 @@ export default function ContractFormComponent({
</span>
</Space>
);
return <></>;
}}
</Form.Item>

View File

@@ -7,7 +7,6 @@ import { useTranslation } from "react-i18next";
import { CHECK_CC_FLEET_NUMBER } from "../../graphql/courtesy-car.queries";
import { DateFormatter } from "../../utils/DateFormatter";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
//import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
@@ -214,9 +213,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
>
<CourtesyCarStatus />
</Form.Item>
<Form.Item label={t("courtesycars.fields.readiness")} name="readiness">
<CourtesyCarReadiness />
</Form.Item>
<div>
<Form.Item
label={t("courtesycars.fields.nextservicekm")}
@@ -231,9 +227,8 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
>
{() => {
const nextservicekm = form.getFieldValue("nextservicekm");
const mileageOver = nextservicekm
? nextservicekm <= form.getFieldValue("mileage")
: false;
const mileageOver =
nextservicekm && nextservicekm <= form.getFieldValue("mileage");
if (mileageOver)
return (
<Space direction="vertical" style={{ color: "tomato" }}>

View File

@@ -34,32 +34,6 @@ const CourtesyCarFuelComponent = (props, ref) => {
step={null}
style={{ marginLeft: "2rem", marginRight: "2rem" }}
{...props}
tooltip={{
formatter: (value) => {
switch (value) {
case 0:
return t("courtesycars.labels.fuel.empty");
case 13:
return t("courtesycars.labels.fuel.18");
case 25:
return t("courtesycars.labels.fuel.14");
case 38:
return t("courtesycars.labels.fuel.38");
case 50:
return t("courtesycars.labels.fuel.12");
case 63:
return t("courtesycars.labels.fuel.58");
case 75:
return t("courtesycars.labels.fuel.34");
case 88:
return t("courtesycars.labels.fuel.78");
case 100:
return t("courtesycars.labels.fuel.full");
default:
return value;
}
},
}}
/>
);
};

View File

@@ -1,35 +0,0 @@
import { Select } from "antd";
import React, { forwardRef, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
const { Option } = Select;
const CourtesyCarReadinessComponent = ({ value, onChange }, ref) => {
const [option, setOption] = useState(value);
const { t } = useTranslation();
useEffect(() => {
if (value !== option && onChange) {
onChange(option);
}
}, [value, option, onChange]);
return (
<Select
allowClear
ref={ref}
value={option}
style={{
width: 100,
}}
onChange={setOption}
>
<Option value="courtesycars.readiness.ready">
{t("courtesycars.readiness.ready")}
</Option>
<Option value="courtesycars.readiness.notready">
{t("courtesycars.readiness.notready")}
</Option>
</Select>
);
};
export default forwardRef(CourtesyCarReadinessComponent);

View File

@@ -17,18 +17,13 @@ import { DateTimeFormatter } from "../../utils/DateFormatter";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const [searchText, setSearchText] = useState("");
const [filter, setFilter] = useLocalStorage(
"filter_courtesy_cars_list",
null
);
const { t } = useTranslation();
const columns = [
@@ -55,7 +50,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
filteredValue: filter?.status || null,
filters: [
{
text: t("courtesycars.status.in"),
@@ -78,34 +72,18 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => {
const { nextservicedate, nextservicekm, mileage, insuranceexpires } =
record;
const { nextservicedate, nextservicekm, mileage } = record;
const mileageOver = nextservicekm ? nextservicekm <= mileage : false;
const mileageOver = nextservicekm <= mileage;
const dueForService =
nextservicedate &&
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
const insuranceOver =
insuranceexpires &&
moment(insuranceexpires).endOf("day").isBefore(moment());
nextservicedate && moment(nextservicedate).isBefore(moment());
return (
<Space>
{t(record.status)}
{(mileageOver || dueForService || insuranceOver) && (
<Tooltip
title={
(mileageOver || dueForService) && insuranceOver
? t("contracts.labels.insuranceexpired") +
" / " +
t("contracts.labels.cardueforservice")
: insuranceOver
? t("contracts.labels.insuranceexpired")
: t("contracts.labels.cardueforservice")
}
>
{(mileageOver || dueForService) && (
<Tooltip title={t("contracts.labels.cardueforservice")}>
<WarningFilled style={{ color: "tomato" }} />
</Tooltip>
)}
@@ -113,27 +91,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
);
},
},
{
title: t("courtesycars.fields.readiness"),
dataIndex: "readiness",
key: "readiness",
sorter: (a, b) => alphaSort(a.readiness, b.readiness),
filteredValue: filter?.readiness || null,
filters: [
{
text: t("courtesycars.readiness.ready"),
value: "courtesycars.readiness.ready",
},
{
text: t("courtesycars.readiness.notready"),
value: "courtesycars.readiness.notready",
},
],
onFilter: (value, record) => value.includes(record.readiness),
sortOrder:
state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order,
render: (text, record) => t(record.readiness),
},
{
title: t("courtesycars.fields.year"),
dataIndex: "year",
@@ -174,36 +131,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
sortOrder:
state.sortedInfo.columnKey === "plate" && state.sortedInfo.order,
},
{
title: t("courtesycars.fields.fuel"),
dataIndex: "fuel",
key: "fuel",
sorter: (a, b) => alphaSort(a.fuel, b.fuel),
sortOrder:
state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order,
render: (text, record) => {
switch (record.fuel) {
case 100:
return t("courtesycars.labels.fuel.full");
case 88:
return t("courtesycars.labels.fuel.78");
case 63:
return t("courtesycars.labels.fuel.58");
case 50:
return t("courtesycars.labels.fuel.12");
case 38:
return t("courtesycars.labels.fuel.34");
case 25:
return t("courtesycars.labels.fuel.14");
case 13:
return t("courtesycars.labels.fuel.18");
case 0:
return t("courtesycars.labels.fuel.empty");
default:
return record.fuel;
}
},
},
{
title: t("courtesycars.labels.outwith"),
dataIndex: "outwith",
@@ -234,8 +161,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, sortedInfo: sorter });
setFilter(filters);
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const tableData = searchText

View File

@@ -5,7 +5,6 @@ import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useLocation } from "react-router-dom";
import { QUERY_CSI_RESPONSE_BY_PK } from "../../graphql/csi.queries";
import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
import ConfigFormComponents from "../config-form-components/config-form-components.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
@@ -45,13 +44,6 @@ export default function CsiResponseFormContainer() {
readOnly
componentList={data.csi_by_pk.csiquestion.config}
/>
{data.csi_by_pk.validuntil ? (
<>
{t("csi.fields.validuntil")}
{": "}
<DateFormatter>{data.csi_by_pk.validuntil}</DateFormatter>
</>
) : null}
</Form>
</Card>
);

View File

@@ -5,11 +5,9 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom";
import { DateFormatter } from "../../utils/DateFormatter";
import { pageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "../../utils/sorters";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
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,
@@ -18,23 +16,23 @@ export default function CsiResponseListPaginated({
total,
}) {
const search = queryString.parse(useLocation().search);
const { responseid } = search;
const { responseid, page, sortcolumn, sortorder } = search;
const history = useHistory();
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
page: "",
});
const { t } = useTranslation();
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
width: "8%",
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => (
<Link to={"/manage/jobs/" + record.job.id}>
{record.job.ro_number || t("general.labels.na")}
@@ -43,18 +41,15 @@ export default function CsiResponseListPaginated({
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner_name",
key: "owner_name",
sorter: (a, b) =>
alphaSort(
OwnerNameDisplayFunction(a.job),
OwnerNameDisplayFunction(b.job)
),
sortOrder:
state.sortedInfo.columnKey === "owner_name" && state.sortedInfo.order,
dataIndex: "owner",
key: "owner",
ellipsis: true,
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
width: "25%",
sortOrder: sortcolumn === "owner" && sortorder,
render: (text, record) => {
return record.job.ownerid ? (
<Link to={"/manage/owners/" + record.job.ownerid}>
return record.job.owner ? (
<Link to={"/manage/owners/" + record.job.owner.id}>
<OwnerNameDisplay ownerObject={record.job} />
</Link>
) : (
@@ -69,9 +64,9 @@ export default function CsiResponseListPaginated({
dataIndex: "completedon",
key: "completedon",
ellipsis: true,
sorter: (a, b) => dateSort(a.completedon, b.completedon),
sortOrder:
state.sortedInfo.columnKey === "completedon" && state.sortedInfo.order,
sorter: (a, b) => a.completedon - b.completedon,
width: "25%",
sortOrder: sortcolumn === "completedon" && sortorder,
render: (text, record) => {
return record.completedon ? (
<DateFormatter>{record.completedon}</DateFormatter>
@@ -81,12 +76,11 @@ export default function CsiResponseListPaginated({
];
const handleTableChange = (pagination, filters, sorter) => {
setState({
...state,
filteredInfo: filters,
sortedInfo: sorter,
page: pagination.current,
});
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
search.page = pagination.current;
search.sortcolumn = sorter.columnKey;
search.sortorder = sorter.order;
history.push({ search: queryString.stringify(search) });
};
const handleOnRowClick = (record) => {
@@ -114,7 +108,7 @@ export default function CsiResponseListPaginated({
pagination={{
position: "top",
pageSize: pageLimit,
current: parseInt(state.page || 1),
current: parseInt(page || 1),
total: total,
}}
columns={columns}
@@ -128,6 +122,13 @@ export default function CsiResponseListPaginated({
selectedRowKeys: [responseid],
type: "radio",
}}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
}, // click row
};
}}
/>
</Card>
);

View File

@@ -3,32 +3,21 @@ import {
ExclamationCircleFilled,
PauseCircleOutlined,
} from "@ant-design/icons";
import { Card, Space, Switch, Table, Tooltip, Typography } from "antd";
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 { TimeFormatter } from "../../../utils/DateFormatter";
import { onlyUnique } from "../../../utils/arrayHelper";
import { alphaSort, dateSort } from "../../../utils/sorters";
import useLocalStorage from "../../../utils/useLocalStorage";
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../../owner-name-display/owner-name-display.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: {},
filteredInfo: {},
});
const [isTvModeScheduledIn, setIsTvModeScheduledIn] = useLocalStorage(
"isTvModeScheduledIn",
false
);
if (!data) return null;
if (!data.scheduled_in_today)
return <DashboardRefreshRequired {...cardProps} />;
@@ -42,12 +31,6 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
alt_transport: item.job.alt_transport,
clm_no: item.job.clm_no,
jobid: item.job.jobid,
joblines_body: item.job.joblines
.filter((l) => l.mod_lbr_ty !== "LAR")
.reduce((acc, val) => acc + val.mod_lb_hrs, 0),
joblines_ref: item.job.joblines
.filter((l) => l.mod_lbr_ty === "LAR")
.reduce((acc, val) => acc + val.mod_lb_hrs, 0),
ins_co_nm: item.job.ins_co_nm,
iouparent: item.job.iouparent,
ownerid: item.job.ownerid,
@@ -66,7 +49,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
v_vin: item.job.v_vin,
vehicleid: item.job.vehicleid,
note: item.note,
start: item.start,
start: moment(item.start).format("hh:mm a"),
title: item.title,
};
appt.push(i);
@@ -76,192 +59,11 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
return new moment(a.start) - new moment(b.start);
});
const tvFontSize = 16;
const tvFontWeight = "bold";
const tvColumns = [
{
title: t("appointments.fields.time"),
dataIndex: "start",
key: "start",
ellipsis: true,
sorter: (a, b) => dateSort(a.start, b.start),
sortOrder:
state.sortedInfo.columnKey === "start" && state.sortedInfo.order,
render: (text, record) => (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
<TimeFormatter>{record.start}</TimeFormatter>
</span>
),
},
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.jobid}
onClick={(e) => e.stopPropagation()}
>
<Space>
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{record.ro_number || t("general.labels.na")}
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
</span>
</Space>
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
ellipsis: true,
sorter: (a, b) =>
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => {
return record.ownerid ? (
<Link
to={"/manage/owners/" + record.ownerid}
onClick={(e) => e.stopPropagation()}
>
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
<OwnerNameDisplay ownerObject={record} />
</span>
</Link>
) : (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
sorter: (a, b) =>
alphaSort(
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
a.v_model_desc || ""
}`,
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
),
sortOrder:
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => {
return record.vehicleid ? (
<Link
to={"/manage/vehicles/" + record.vehicleid}
onClick={(e) => e.stopPropagation()}
>
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</span>
</Link>
) : (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{`${
record.v_model_yr || ""
} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}</span>
);
},
},
{
title: t("appointments.fields.alt_transport"),
dataIndex: "alt_transport",
key: "alt_transport",
ellipsis: true,
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
sortOrder:
state.sortedInfo.columnKey === "alt_transport" &&
state.sortedInfo.order,
filters:
(appt &&
appt
.map((j) => j.alt_transport)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Alt. Transport",
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
[],
onFilter: (value, record) => value.includes(record.alt_transport),
render: (text, record) => (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{record.alt_transport}
</span>
),
},
{
title: t("jobs.fields.lab"),
dataIndex: "joblines_body",
key: "joblines_body",
sorter: (a, b) => a.joblines_body - b.joblines_body,
sortOrder:
state.sortedInfo.columnKey === "joblines_body" &&
state.sortedInfo.order,
align: "right",
render: (text, record) => (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{record.joblines_body.toFixed(1)}
</span>
),
},
{
title: t("jobs.fields.lar"),
dataIndex: "joblines_ref",
key: "joblines_ref",
sorter: (a, b) => a.joblines_ref - b.joblines_ref,
sortOrder:
state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order,
align: "right",
render: (text, record) => (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{record.joblines_ref.toFixed(1)}
</span>
),
},
];
const columns = [
{
title: t("appointments.fields.time"),
dataIndex: "start",
key: "start",
ellipsis: true,
sorter: (a, b) => dateSort(a.start, b.start),
sortOrder:
state.sortedInfo.columnKey === "start" && state.sortedInfo.order,
render: (text, record) => <TimeFormatter>{record.start}</TimeFormatter>,
},
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.jobid}
@@ -289,10 +91,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
dataIndex: "owner",
key: "owner",
ellipsis: true,
sorter: (a, b) =>
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
responsive: ["md"],
render: (text, record) => {
return record.ownerid ? (
<Link
@@ -309,16 +108,23 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
},
},
{
title: t("dashboard.labels.phone"),
dataIndex: "ownr_ph",
key: "ownr_ph",
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<Space size="small" wrap>
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
</Space>
<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} />
),
},
{
@@ -328,7 +134,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<a href={`mailto:${record.ownr_ea}`}>{record.ownr_ea}</a>
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
),
},
{
@@ -336,15 +142,6 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
sorter: (a, b) =>
alphaSort(
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
a.v_model_desc || ""
}`,
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
),
sortOrder:
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => {
return record.vehicleid ? (
<Link
@@ -368,80 +165,43 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
key: "ins_co_nm",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
sortOrder:
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
filters:
(appt &&
appt
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Ins. Co.*",
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
},
{
title: t("appointments.fields.time"),
dataIndex: "start",
key: "start",
ellipsis: true,
responsive: ["md"],
},
{
title: t("appointments.fields.alt_transport"),
dataIndex: "alt_transport",
key: "alt_transport",
ellipsis: true,
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
sortOrder:
state.sortedInfo.columnKey === "alt_transport" &&
state.sortedInfo.order,
filters:
(appt &&
appt
.map((j) => j.alt_transport)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Alt. Transport",
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
[],
onFilter: (value, record) => value.includes(record.alt_transport),
responsive: ["md"],
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
const handleTableChange = (sorter) => {
setState({ ...state, sortedInfo: sorter });
};
return (
<Card
title={t("dashboard.titles.scheduledindate", {
title={t("dashboard.titles.scheduledintoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
})}
extra={
<Space>
<Typography.Text>{t("general.labels.tvmode")}</Typography.Text>
<Switch
onClick={() => setIsTvModeScheduledIn(!isTvModeScheduledIn)}
defaultChecked={isTvModeScheduledIn}
/>
</Space>
}
{...cardProps}
>
<div style={{ height: "100%" }}>
<Table
onChange={handleTableChange}
pagination={false}
columns={isTvModeScheduledIn ? tvColumns : columns}
pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns}
scroll={{ x: true, y: "calc(100% - 2em)" }}
rowKey="id"
style={{ height: "85%" }}
dataSource={appt}
size={isTvModeScheduledIn ? "small" : "middle"}
/>
</div>
</Card>
@@ -460,10 +220,6 @@ export const DashboardScheduledInTodayGql = `
alt_transport
clm_no
jobid: id
joblines(where: {removed: {_eq: false}}) {
mod_lb_hrs
mod_lbr_ty
}
ins_co_nm
iouparent
ownerid

View File

@@ -3,272 +3,37 @@ import {
ExclamationCircleFilled,
PauseCircleOutlined,
} from "@ant-design/icons";
import { Card, Space, Switch, Table, Tooltip, Typography } from "antd";
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 { TimeFormatter } from "../../../utils/DateFormatter";
import { onlyUnique } from "../../../utils/arrayHelper";
import { alphaSort, dateSort } from "../../../utils/sorters";
import useLocalStorage from "../../../utils/useLocalStorage";
import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../../owner-name-display/owner-name-display.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: {},
filteredInfo: {},
});
const [isTvModeScheduledOut, setIsTvModeScheduledOut] = useLocalStorage(
"isTvModeScheduledOut",
false
);
if (!data) return null;
if (!data.scheduled_out_today)
return <DashboardRefreshRequired {...cardProps} />;
data.scheduled_out_today.forEach((item) => {
item.joblines_body = item.joblines
? item.joblines
.filter((l) => l.mod_lbr_ty !== "LAR")
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
: 0;
item.joblines_ref = item.joblines
? item.joblines
.filter((l) => l.mod_lbr_ty === "LAR")
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
: 0;
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 tvFontSize = 18;
const tvFontWeight = "bold";
const tvColumns = [
{
title: t("jobs.fields.scheduled_completion"),
dataIndex: "scheduled_completion",
key: "scheduled_completion",
ellipsis: true,
sorter: (a, b) =>
dateSort(a.scheduled_completion, b.scheduled_completion),
sortOrder:
state.sortedInfo.columnKey === "scheduled_completion" &&
state.sortedInfo.order,
render: (text, record) => (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
</span>
),
},
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.jobid}
onClick={(e) => e.stopPropagation()}
>
<Space>
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{record.ro_number || t("general.labels.na")}
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
</span>
</Space>
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
ellipsis: true,
sorter: (a, b) =>
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => {
return record.ownerid ? (
<Link
to={"/manage/owners/" + record.ownerid}
onClick={(e) => e.stopPropagation()}
>
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
<OwnerNameDisplay ownerObject={record} />
</span>
</Link>
) : (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
<OwnerNameDisplay ownerObject={record} />
</span>
);
},
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
sorter: (a, b) =>
alphaSort(
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
a.v_model_desc || ""
}`,
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
),
sortOrder:
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => {
return record.vehicleid ? (
<Link
to={"/manage/vehicles/" + record.vehicleid}
onClick={(e) => e.stopPropagation()}
>
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</span>
</Link>
) : (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>{`${
record.v_model_yr || ""
} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`}</span>
);
},
},
{
title: t("appointments.fields.alt_transport"),
dataIndex: "alt_transport",
key: "alt_transport",
ellipsis: true,
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
sortOrder:
state.sortedInfo.columnKey === "alt_transport" &&
state.sortedInfo.order,
filters:
(data.scheduled_out_today &&
data.scheduled_out_today
.map((j) => j.alt_transport)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Alt. Transport*",
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
[],
onFilter: (value, record) => value.includes(record.alt_transport),
render: (text, record) => (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{record.alt_transport}
</span>
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
ellipsis: true,
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filters:
(data.scheduled_out_today &&
data.scheduled_out_today
.map((j) => j.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
[],
onFilter: (value, record) => value.includes(record.status),
render: (text, record) => (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{record.status}
</span>
),
},
{
title: t("jobs.fields.lab"),
dataIndex: "joblines_body",
key: "joblines_body",
sorter: (a, b) => a.joblines_body - b.joblines_body,
sortOrder:
state.sortedInfo.columnKey === "joblines_body" &&
state.sortedInfo.order,
align: "right",
render: (text, record) => (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{record.joblines_body.toFixed(1)}
</span>
),
},
{
title: t("jobs.fields.lar"),
dataIndex: "joblines_ref",
key: "joblines_ref",
sorter: (a, b) => a.joblines_ref - b.joblines_ref,
sortOrder:
state.sortedInfo.columnKey === "joblines_ref" && state.sortedInfo.order,
align: "right",
render: (text, record) => (
<span style={{ fontSize: tvFontSize, fontWeight: tvFontWeight }}>
{record.joblines_ref.toFixed(1)}
</span>
),
},
];
const columns = [
{
title: t("jobs.fields.scheduled_completion"),
dataIndex: "scheduled_completion",
key: "scheduled_completion",
ellipsis: true,
sorter: (a, b) =>
dateSort(a.scheduled_completion, b.scheduled_completion),
sortOrder:
state.sortedInfo.columnKey === "scheduled_completion" &&
state.sortedInfo.order,
render: (text, record) => (
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
),
},
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.jobid}
@@ -296,10 +61,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
dataIndex: "owner",
key: "owner",
ellipsis: true,
sorter: (a, b) =>
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
responsive: ["md"],
render: (text, record) => {
return record.ownerid ? (
<Link
@@ -316,16 +78,23 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
},
},
{
title: t("dashboard.labels.phone"),
dataIndex: "ownr_ph",
key: "ownr_ph",
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<Space size="small" wrap>
<ChatOpenButton phone={record.ownr_ph1} jobid={record.jobid} />
<ChatOpenButton phone={record.ownr_ph2} jobid={record.jobid} />
</Space>
<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} />
),
},
{
@@ -335,7 +104,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<a href={`mailto:${record.ownr_ea}`}>{record.ownr_ea}</a>
<ChatOpenButton phone={record.ownr_ea} jobid={record.jobid} />
),
},
{
@@ -343,15 +112,6 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
sorter: (a, b) =>
alphaSort(
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
a.v_model_desc || ""
}`,
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
),
sortOrder:
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => {
return record.vehicleid ? (
<Link
@@ -375,80 +135,43 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
key: "ins_co_nm",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
sortOrder:
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
filters:
(data.scheduled_out_today &&
data.scheduled_out_today
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Ins. Co.*",
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
},
{
title: t("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,
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
sortOrder:
state.sortedInfo.columnKey === "alt_transport" &&
state.sortedInfo.order,
filters:
(data.scheduled_out_today &&
data.scheduled_out_today
.map((j) => j.alt_transport)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Alt. Transport*",
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
[],
onFilter: (value, record) => value.includes(record.alt_transport),
responsive: ["md"],
},
];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
const handleTableChange = (sorter) => {
setState({ ...state, sortedInfo: sorter });
};
return (
<Card
title={t("dashboard.titles.scheduledoutdate", {
title={t("dashboard.titles.scheduledouttoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
})}
extra={
<Space>
<Typography.Text>{t("general.labels.tvmode")}</Typography.Text>
<Switch
onClick={() => setIsTvModeScheduledOut(!isTvModeScheduledOut)}
defaultChecked={isTvModeScheduledOut}
/>
</Space>
}
{...cardProps}
>
<div style={{ height: "100%" }}>
<Table
onChange={handleTableChange}
pagination={false}
columns={isTvModeScheduledOut ? tvColumns : columns}
pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns}
scroll={{ x: true, y: "calc(100% - 2em)" }}
rowKey="id"
style={{ height: "85%" }}
dataSource={data.scheduled_out_today}
size={isTvModeScheduledOut ? "small" : "middle"}
/>
</div>
</Card>
@@ -465,10 +188,6 @@ export const DashboardScheduledOutTodayGql = `
alt_transport
clm_no
jobid: id
joblines(where: {removed: {_eq: false}}) {
mod_lb_hrs
mod_lbr_ty
}
ins_co_nm
iouparent
ownerid
@@ -481,7 +200,6 @@ export const DashboardScheduledOutTodayGql = `
production_vars
ro_number
scheduled_completion
status
suspended
v_make_desc
v_model_desc

View File

@@ -275,22 +275,26 @@ const componentList = {
h: 2,
},
ScheduleInToday: {
label: i18next.t("dashboard.titles.scheduledintoday"),
label: i18next.t("dashboard.titles.scheduledintoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
}),
component: DashboardScheduledInToday,
gqlFragment: DashboardScheduledInTodayGql,
minW: 6,
minW: 10,
minH: 2,
w: 10,
h: 3,
h: 2,
},
ScheduleOutToday: {
label: i18next.t("dashboard.titles.scheduledouttoday"),
label: i18next.t("dashboard.titles.scheduledouttoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
}),
component: DashboardScheduledOutToday,
gqlFragment: DashboardScheduledOutTodayGql,
minW: 6,
minW: 10,
minH: 2,
w: 10,
h: 3,
h: 2,
},
};
@@ -302,7 +306,8 @@ const createDashboardQuery = (state) => {
.map((item, index) => componentList[item.i].gqlFragment || "")
.join("");
return gql`
query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""}
query QUERY_DASHBOARD_DETAILS {
${componentBasedAdditions || ""}
monthly_sales: jobs(where: {_and: [
{ voided: {_eq: false}},
{date_invoiced: {_gte: "${moment()
@@ -312,11 +317,11 @@ const createDashboardQuery = (state) => {
.endOf("month")
.endOf("day")
.toISOString()}"}}]}) {
id
ro_number
date_invoiced
job_totals
rate_la1
id
ro_number
date_invoiced
job_totals
rate_la1
rate_la2
rate_la3
rate_la4
@@ -339,42 +344,43 @@ const createDashboardQuery = (state) => {
rate_mapa
rate_mash
rate_matd
joblines(where: { removed: { _eq: false } }) {
joblines(where: { removed: { _eq: false } }) {
id
mod_lbr_ty
mod_lb_hrs
act_price
part_qty
part_type
}
}
production_jobs: jobs(where: { inproduction: { _eq: true } }) {
id
ro_number
ins_co_nm
job_totals
joblines(where: { removed: { _eq: false } }) {
}
production_jobs: jobs(where: { inproduction: { _eq: true } }) {
id
mod_lbr_ty
mod_lb_hrs
act_price
part_qty
part_type
}
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
aggregate {
sum {
mod_lb_hrs
ro_number
ins_co_nm
job_totals
joblines(where: { removed: { _eq: false } }) {
id
mod_lbr_ty
mod_lb_hrs
act_price
part_qty
part_type
}
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
aggregate {
sum {
mod_lb_hrs
}
}
}
}
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
aggregate {
sum {
mod_lb_hrs
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
aggregate {
sum {
mod_lb_hrs
}
}
}
}
}
}`;
`;
};

View File

@@ -128,7 +128,7 @@
height: 100%;
width: 100%;
.ant-card-body {
height: calc(100% - 2rem);
height: 80%;
width: 100%;
// // background-color: red;
// height: 90%;

View File

@@ -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);

View File

@@ -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);

View File

@@ -2,7 +2,7 @@ import { Button, Col, Collapse, Result, Row, Space } from "antd";
import React from "react";
import { withTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import * as Sentry from "@sentry/react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
@@ -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**
// ----
@@ -138,6 +138,7 @@ class ErrorBoundary extends React.Component {
}
}
export default Sentry.withErrorBoundary(
connect(mapStateToProps, mapDispatchToProps)(withTranslation()(ErrorBoundary))
);
export default connect(
mapStateToProps,
mapDispatchToProps
)(withTranslation()(ErrorBoundary));

View File

@@ -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);

View File

@@ -14,7 +14,6 @@ import Icon, {
//GlobalOutlined,
HomeFilled,
ImportOutlined,
// InfoCircleOutlined,
LineChartOutlined,
PaperClipOutlined,
PhoneOutlined,
@@ -27,11 +26,8 @@ import Icon, {
UserOutlined,
} from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Layout,
Menu, // Switch, Tooltip
} from "antd";
import React from "react"; //, { useEffect, useState }
import { Layout, Menu } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { BsKanban } from "react-icons/bs";
import {
@@ -56,7 +52,6 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
//import { handleBeta, setBeta, checkBeta } from "../../utils/handleBeta";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -107,21 +102,9 @@ function Header({
{},
bodyshop && bodyshop.imexshopid
);
//const [betaSwitch, setBetaSwitch] = useState(false);
const { t } = useTranslation();
// useEffect(() => {
// const isBeta = checkBeta();
// setBetaSwitch(isBeta);
// }, []);
// const betaSwitchChange = (checked) => {
// setBeta(checked);
// setBetaSwitch(checked);
// handleBeta();
// };
return (
<Layout.Header>
<Menu
@@ -285,13 +268,6 @@ function Header({
{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} />}
@@ -406,12 +382,20 @@ function Header({
<Menu.Item
key="help"
onClick={() => {
window.open("https://rometech.com/", "_blank");
window.open("https://help.imex.online/", "_blank");
}}
icon={<Icon component={QuestionCircleFilled} />}
>
{t("menus.header.help")}
</Menu.Item>
<Menu.Item
key="rescue"
onClick={() => {
window.open("https://imexrescue.com/", "_blank");
}}
>
{t("menus.header.rescueme")}
</Menu.Item>
<Menu.Item key="shiftclock">
<Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
</Menu.Item>
@@ -447,18 +431,6 @@ function Header({
</Menu.Item>
))}
</Menu.SubMenu>
{
// <Menu.Item style={{marginLeft: 'auto'}} key="profile">
// <Tooltip title="A more modern ImEX Online is ready for you to try! You can switch back at any time.">
// <InfoCircleOutlined/>
// <span style={{marginRight: 8}}>Try the new ImEX Online</span>
// <Switch
// checked={betaSwitch}
// onChange={betaSwitchChange}
// />
// </Tooltip>
// </Menu.Item>
}
</Menu>
</Layout.Header>
);

View File

@@ -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);

View File

@@ -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 (

View File

@@ -7,31 +7,21 @@ import { connect } from "react-redux";
import { useHistory } from "react-router";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_LINES_IOU } from "../../graphql/jobs-lines.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { CreateIouForJob } from "../jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(JobCreateIOU);
export function JobCreateIOU({
bodyshop,
currentUser,
job,
selectedJobLines,
technician,
}) {
export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const client = useApolloClient();
@@ -89,19 +79,13 @@ export function JobCreateIOU({
title={t("jobs.labels.createiouwarning")}
onConfirm={handleCreateIou}
disabled={
!selectedJobLines ||
selectedJobLines.length === 0 ||
!job.converted ||
technician
!selectedJobLines || selectedJobLines.length === 0 || !job.converted
}
>
<Button
loading={loading}
disabled={
!selectedJobLines ||
selectedJobLines.length === 0 ||
!job.converted ||
technician
!selectedJobLines || selectedJobLines.length === 0 || !job.converted
}
>
{t("jobs.actions.createiou")}

View File

@@ -32,9 +32,9 @@ const mapDispatchToProps = (dispatch) => ({
});
const span = {
lg: { span: 24 },
xl: { span: 12 },
xxl: { span: 8 },
sm: { span: 24 },
md: { span: 12 },
lg: { span: 8 },
};
export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
@@ -137,6 +137,12 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<JobDetailCardsPartsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<JobDetailCardsNotesComponent
loading={loading}
@@ -157,12 +163,6 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col span={24}>
<JobDetailCardsPartsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
</Row>
</Card>
) : null}

View File

@@ -1,119 +1,16 @@
import { Table } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
import PartsStatusPie from "../parts-status-pie/parts-status-pie.component";
import CardTemplate from "./job-detail-cards.template.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort } from "../../utils/sorters";
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
jobRO: selectJobReadOnly,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobDetailCardsPartsComponent);
export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
export default function JobDetailCardsPartsComponent({ loading, data }) {
const { t } = useTranslation();
const { joblines_status } = data;
const columns = [
{
title: t("joblines.fields.line_desc"),
dataIndex: "line_desc",
fixed: "left",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
onCell: (record) => ({
className: record.manual_line && "job-line-manual",
style: {
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}),
},
}),
width: "30%",
ellipsis: true,
},
{
title: t("joblines.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
width: "15%",
sorter: (a, b) =>
alphaSort(
t(`joblines.fields.part_types.${a.part_type}`),
t(`joblines.fields.part_types.${b.part_type}`)
),
render: (text, record) =>
record.part_type
? t(`joblines.fields.part_types.${record.part_type}`)
: null,
},
{
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty",
width: "10%",
},
{
title: t("joblines.fields.notes"),
dataIndex: "notes",
key: "notes",
render: (text, record) => (
<JobLineNotePopup disabled={jobRO} jobline={record} />
),
},
{
title: t("joblines.fields.location"),
dataIndex: "location",
key: "location",
sorter: (a, b) => alphaSort(a.location, b.location),
render: (text, record) => (
<JobLineLocationPopup jobline={record} disabled={jobRO} />
),
},
{
title: t("joblines.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
filters:
(data &&
data.joblines
?.map((l) => l.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
render: (text, record) => (
<JobLineStatusPopup jobline={record} disabled={jobRO} />
),
},
];
return (
<div>
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
<PartsStatusPie joblines_status={joblines_status} />
<Table
key="id"
columns={columns}
dataSource={data ? data.joblines : []}
/>
</CardTemplate>
</div>
);

View File

@@ -1,37 +1,15 @@
import { Card } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Card } from "antd";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectTechnician } from "../../redux/tech/tech.selectors";
const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobDetailCardTemplate);
export function JobDetailCardTemplate({
export default function JobDetailCardTemplate({
loading,
title,
extraLink,
technician,
...otherProps
}) {
const { t } = useTranslation();
let extra;
if (extraLink && !technician)
extra = {
extra: <Link to={extraLink}>{t("jobs.labels.cards.more")}</Link>,
};
if (extraLink) extra = { extra: <Link to={extraLink}>More</Link> };
return (
<Card
size="small"

View File

@@ -8,18 +8,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
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))
});
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
export function JobLinesExpander({ jobline, jobid, bodyshop }) {
export default function JobLinesExpander({ jobline, jobid }) {
const { t } = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only",
@@ -34,7 +23,7 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
return (
<Row>
<Col md={24} lg={8}>
<Col md={24} lg={12}>
<Typography.Title level={4}>
{t("parts_orders.labels.parts_orders")}
</Typography.Title>
@@ -60,7 +49,7 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
)}
</Timeline>
</Col>
<Col md={24} lg={8}>
<Col md={24} lg={12}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline>
{data.billlines.length > 0 ? (
@@ -82,7 +71,7 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_cost")}: `}
{`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span>
</Col>
@@ -100,37 +89,6 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
)}
</Timeline>
</Col>
<Col md={24} lg={8}>
<Typography.Title level={4}>
{t("parts_dispatch.labels.parts_dispatch")}
</Typography.Title>
<Timeline>
{data.parts_dispatch_lines.length > 0 ? (
data.parts_dispatch_lines.map((line) => (
<Timeline.Item key={line.id}>
<Space split={<Divider type="vertical" />} wrap>
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>
{line.parts_dispatch.number}
</Link>
{
bodyshop.employees.find(
(e) => e.id === line.parts_dispatch.employeeid
)?.first_name
}
<Space>
{t("parts_dispatch_lines.fields.accepted_at")}
<DateFormatter>{line.accepted_at}</DateFormatter>
</Space>
</Space>
</Timeline.Item>
))
) : (
<Timeline.Item>
{t("parts_orders.labels.notyetordered")}
</Timeline.Item>
)}
</Timeline>
</Col>
</Row>
);
}

View File

@@ -1,97 +0,0 @@
import { useMutation } from "@apollo/client";
import { Button, Form, notification, Popover, Tooltip } from "antd";
import { t } from "i18next";
import React, { useState } from "react";
import { UPDATE_LINE_PPC } from "../../graphql/jobs-lines.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
import axios from "axios";
export default function JobLinesPartPriceChange({ job, line, refetch }) {
const [loading, setLoading] = useState(false);
const [updatePartPrice] = useMutation(UPDATE_LINE_PPC);
const handleFinish = async (values) => {
try {
setLoading(true);
const result = await updatePartPrice({
variables: {
id: line.id,
jobline: {
act_price_before_ppc: line.act_price_before_ppc
? line.act_price_before_ppc
: line.act_price,
act_price: values.act_price,
},
},
});
await axios.post("/job/totalsssu", {
id: job.id,
});
if (result.errors) {
notification.open({
type: "error",
message: t("joblines.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
if (refetch) refetch();
} else {
notification.open({
type: "success",
message: t("joblines.successes.saved"),
});
}
} catch (error) {
notification.open({
type: "error",
message: t("joblines.errors.saving", { error: JSON.stringify(error) }),
});
} finally {
setLoading(false);
}
};
const popcontent = (
<Form layout="vertical" onFinish={handleFinish} initialValues={{ act_price: line.act_price }}>
<Form.Item
name="act_price"
label={t("jobs.labels.act_price_ppc")}
rules={[{ required: true }]}
>
<CurrencyFormItemComponent />
</Form.Item>
<Button loading={loading} htmlType="primary">
{t("general.actions.save")}
</Button>
</Form>
);
return (
<JobLineConvertToLabor jobline={line} job={job}>
<Popover trigger="click" disabled={line.manual_line} content={popcontent}>
<CurrencyFormatter>
{line.db_ref === "900510" || line.db_ref === "900511"
? line.prt_dsmk_m
: line.act_price}
</CurrencyFormatter>
{line.prt_dsmk_p && line.prt_dsmk_p !== 0 ? (
<span style={{ marginLeft: ".2rem" }}>{`(${line.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
{line.act_price_before_ppc && line.act_price_before_ppc !== 0 ? (
<Tooltip title={t("jobs.labels.ppc")}>
<span style={{ marginLeft: ".2rem", color: "tomato" }}>
(
<CurrencyFormatter>{line.act_price_before_ppc}</CurrencyFormatter>
)
</span>
</Tooltip>
) : (
<></>
)}
</Popover>
</JobLineConvertToLabor>
);
}

View File

@@ -1,12 +1,12 @@
import {
DeleteFilled,
EditFilled,
FilterFilled,
HomeOutlined,
MinusCircleTwoTone,
PlusCircleTwoTone,
SyncOutlined,
WarningFilled,
EditFilled,
PlusCircleTwoTone,
MinusCircleTwoTone,
HomeOutlined,
} from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import {
@@ -29,6 +29,7 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { onlyUnique } from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
@@ -37,18 +38,13 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
// import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
import _ from "lodash";
import moment from "moment";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-send-parts-price-change.component";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import _ from "lodash";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLinesExpander from "./job-lines-expander.component";
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
import JoblineTeamAssignment from "../job-line-team-assignment/job-line-team-assignmnent.component";
import JobLineDispatchButton from "../job-line-dispatch-button/job-line-dispatch-button.component";
import JobLineBulkAssignComponent from "../job-line-bulk-assign/job-line-bulk-assign.component";
import { useTreatments } from "@splitsoftware/splitio-react";
import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -80,16 +76,8 @@ export function JobLinesComponent({
setBillEnterContext,
}) {
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
const { Enhanced_Payroll } = useTreatments(
["Enhanced_Payroll"],
{},
bodyshop.imexshopid
);
const [selectedLines, setSelectedLines] = useState([]);
console.log(
"🚀 ~ file: job-lines.component.jsx:89 ~ selectedLines:",
selectedLines
);
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {},
@@ -133,21 +121,10 @@ export function JobLinesComponent({
sortOrder:
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
ellipsis: true,
onCell: (record) => ({
className: record.manual_line && "job-line-manual",
style: {
...(record.parts_dispatch_lines[0]?.accepted_at
? { boxShadow: " -.5em 0 0 #FFC107" }
: {}),
},
}),
render: (text, record) => (
<span class="ant-table-cell-content">
{`${record.oem_partno || ""} ${
record.alt_partno ? `(${record.alt_partno})` : ""
}`.trim()}
</span>
),
render: (text, record) =>
`${record.oem_partno || ""} ${
record.alt_partno ? `(${record.alt_partno})` : ""
}`.trim(),
},
{
title: t("joblines.fields.op_code_desc"),
@@ -243,7 +220,20 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) => (
<JobLinesPartPriceChange line={record} job={job} refetch={refetch} />
<JobLineConvertToLabor jobline={record} job={job}>
<CurrencyFormatter>
{record.db_ref === "900510" || record.db_ref === "900511"
? record.prt_dsmk_m
: record.act_price}
</CurrencyFormatter>
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
<span
style={{ marginLeft: ".2rem" }}
>{`(${record.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
</JobLineConvertToLabor>
),
},
{
@@ -296,23 +286,6 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order,
responsive: ["md"],
},
...(Enhanced_Payroll.treatment === "on"
? [
{
title: t("joblines.fields.assigned_team"),
dataIndex: "assigned_team",
key: "assigned_team",
render: (text, record) => (
<JoblineTeamAssignment
disabled={jobRO}
jobline={record}
jobId={job.id}
/>
),
},
]
: []),
{
title: t("joblines.fields.notes"),
dataIndex: "notes",
@@ -431,11 +404,7 @@ export function JobLinesComponent({
setSelectedLines((selectedLines) =>
_.uniq([
...selectedLines,
...jobLines.filter(
(item) =>
markedTypes.includes(item.part_type) ||
markedTypes.includes(item.mod_lbr_ty)
),
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
])
);
}
@@ -448,21 +417,6 @@ export function JobLinesComponent({
<Menu.Item key="PAL">{t("joblines.fields.part_types.PAL")}</Menu.Item>
<Menu.Item key="PAS">{t("joblines.fields.part_types.PAS")}</Menu.Item>
<Menu.Divider />
<Menu.Item key="LAA">{t("joblines.fields.lbr_types.LAA")}</Menu.Item>
<Menu.Item key="LAB">{t("joblines.fields.lbr_types.LAB")}</Menu.Item>
<Menu.Item key="LAD">{t("joblines.fields.lbr_types.LAD")}</Menu.Item>
<Menu.Item key="LAE">{t("joblines.fields.lbr_types.LAE")}</Menu.Item>
<Menu.Item key="LAF">{t("joblines.fields.lbr_types.LAF")}</Menu.Item>
<Menu.Item key="LAG">{t("joblines.fields.lbr_types.LAG")}</Menu.Item>
<Menu.Item key="LAM">{t("joblines.fields.lbr_types.LAM")}</Menu.Item>
<Menu.Item key="LAR">{t("joblines.fields.lbr_types.LAR")}</Menu.Item>
<Menu.Item key="LAS">{t("joblines.fields.lbr_types.LAS")}</Menu.Item>
<Menu.Item key="LAU">{t("joblines.fields.lbr_types.LAU")}</Menu.Item>
<Menu.Item key="LA1">{t("joblines.fields.lbr_types.LA1")}</Menu.Item>
<Menu.Item key="LA2">{t("joblines.fields.lbr_types.LA2")}</Menu.Item>
<Menu.Item key="LA3">{t("joblines.fields.lbr_types.LA3")}</Menu.Item>
<Menu.Item key="LA4">{t("joblines.fields.lbr_types.LA2")}</Menu.Item>
<Menu.Divider />
<Menu.Item key="clear">{t("general.labels.clear")}</Menu.Item>
</Menu>
);
@@ -486,18 +440,6 @@ export function JobLinesComponent({
</Space>
</Tag>
)}
<JobLineDispatchButton
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
job={job}
/>
{Enhanced_Payroll.treatment === "on" && (
<JobLineBulkAssignComponent
selectedLines={selectedLines}
setSelectedLines={setSelectedLines}
job={job}
/>
)}
<Button
disabled={
(job && !job.converted) ||
@@ -506,6 +448,15 @@ export function JobLinesComponent({
technician
}
onClick={() => {
// setPartsOrderContext({
// actions: { refetch: refetch },
// context: {
// jobId: job.id,
// job: job,
// linesToOrder: selectedLines,
// },
// });
setBillEnterContext({
actions: { refetch: refetch },
context: {
@@ -612,9 +563,6 @@ export function JobLinesComponent({
>
{t("joblines.actions.new")}
</Button>
{bodyshop.region_config.toLowerCase().startsWith("us") && (
<JobSendPartPriceChangeComponent job={job} />
)}
<JobCreateIOU job={job} selectedJobLines={selectedLines} />
<Input.Search
placeholder={t("general.labels.search")}

View File

@@ -1,246 +0,0 @@
import React, {useCallback, useEffect, useState} from 'react';
import moment from "moment";
import axios from 'axios';
import {Badge, Card, Space, Table, Tag} from 'antd';
import {gql, useQuery} from "@apollo/client";
import {DateTimeFormatterFunction} from "../../utils/DateFormatter";
import {isEmpty} from "lodash";
import {useTranslation} from "react-i18next";
require('./job-lifecycle.styles.scss');
// show text on bar if text can fit
export function JobLifecycleComponent({job, statuses, ...rest}) {
const [loading, setLoading] = useState(true);
const [lifecycleData, setLifecycleData] = useState(null);
const {t} = useTranslation(); // Used for tracking external state changes.
const {data} = useQuery(gql`
query get_job_test($id: uuid!){
jobs_by_pk(id:$id){
id
status
}
}
`, {
variables: {
id: job.id
},
fetchPolicy: 'cache-only'
});
/**
* Gets the lifecycle data for the job.
* @returns {Promise<void>}
*/
const getLifecycleData = useCallback(async () => {
if (job && job.id && statuses && statuses.statuses) {
try {
setLoading(true);
const response = await axios.post("/job/lifecycle", {
jobids: job.id,
statuses: statuses.statuses
});
const data = response.data.transition[job.id];
setLifecycleData(data);
} catch (err) {
console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`);
} finally {
setLoading(false);
}
}
}, [job, statuses, t]);
useEffect(() => {
if (!data) return;
setTimeout(() => {
getLifecycleData().catch(err => console.error(`${t('job_lifecycle.errors.fetch')}: ${err.message}`));
}, 500);
}, [data, getLifecycleData, t]);
const columns = [
{
title: t('job_lifecycle.columns.value'),
dataIndex: 'value',
key: 'value',
},
{
title: t('job_lifecycle.columns.start'),
dataIndex: 'start',
key: 'start',
render: (text) => DateTimeFormatterFunction(text),
sorter: (a, b) => moment(a.start).unix() - moment(b.start).unix(),
},
{
title: t('job_lifecycle.columns.relative_start'),
dataIndex: 'start_readable',
key: 'start_readable',
},
{
title: t('job_lifecycle.columns.end'),
dataIndex: 'end',
key: 'end',
sorter: (a, b) => {
if (isEmpty(a.end) || isEmpty(b.end)) {
if (isEmpty(a.end) && isEmpty(b.end)) {
return 0;
}
return isEmpty(a.end) ? 1 : -1;
}
return moment(a.end).unix() - moment(b.end).unix();
},
render: (text) => isEmpty(text) ? t('job_lifecycle.content.not_available') : DateTimeFormatterFunction(text)
},
{
title: t('job_lifecycle.columns.relative_end'),
dataIndex: 'end_readable',
key: 'end_readable',
},
{
title: t('job_lifecycle.columns.duration'),
dataIndex: 'duration_readable',
key: 'duration_readable',
sorter: (a, b) => a.duration - b.duration,
},
];
return (
<Card loading={loading} title={t('job_lifecycle.content.title')}>
{!loading ? (
lifecycleData && lifecycleData.lifecycle && lifecycleData.durations ? (
<Space direction='vertical' style={{width: '100%'}}>
<Card
type='inner'
title={(
<Space direction='horizontal' size='small'>
<Badge status='processing' count={lifecycleData.durations.totalStatuses}/>
{t('job_lifecycle.content.title_durations')}
</Space>
)}
style={{width: '100%'}}
>
<div id="bar-container" style={{
display: 'flex',
width: '100%',
height: '100px',
textAlign: 'center',
borderRadius: '5px',
borderWidth: '5px',
borderStyle: 'solid',
borderColor: '#f0f2f5',
margin: 0,
padding: 0
}}>
{lifecycleData.durations.summations.map((key, index, array) => {
const isFirst = index === 0;
const isLast = index === array.length - 1;
return (
<div key={key.status} style={{
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
margin: 0,
padding: 0,
borderTop: '1px solid #f0f2f5',
borderBottom: '1px solid #f0f2f5',
borderLeft: isFirst ? '1px solid #f0f2f5' : undefined,
borderRight: isLast ? '1px solid #f0f2f5' : undefined,
backgroundColor: key.color,
width: `${key.percentage}%`
}}
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
>
{key.percentage > 15 ?
<>
<div>{key.roundedPercentage}</div>
<div style={{
backgroundColor: '#f0f2f5',
borderRadius: '5px',
paddingRight: '2px',
paddingLeft: '2px',
fontSize: '0.8rem',
}}>
{key.status}
</div>
</>
: null}
</div>
);
})}
</div>
<Card type='inner' title={t('job_lifecycle.content.legend_title')}
style={{marginTop: '10px'}}>
<div>
{lifecycleData.durations.summations.map((key) => (
<Tag color={key.color} style={{width: '13vh', padding: '4px', margin: '4px'}}>
<div
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
style={{
backgroundColor: '#f0f2f5',
color: '#000',
padding: '4px',
textAlign: 'center'
}}>
{key.status} ({key.roundedPercentage})
</div>
</Tag>
))}
</div>
</Card>
{(lifecycleData?.durations?.humanReadableTotal) ||
(lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable) ?
<Card style={{marginTop: '10px'}}>
<ul>
{lifecycleData.durations && lifecycleData.durations.humanReadableTotal &&
<li>
<span
style={{fontWeight: 'bold'}}>{t('job_lifecycle.content.previous_status_accumulated_time')}:</span> {lifecycleData.durations.humanReadableTotal}
</li>
}
{lifecycleData.lifecycle[0] && lifecycleData.lifecycle[0].value && lifecycleData?.durations?.totalCurrentStatusDuration?.humanReadable &&
<li>
<span
style={{fontWeight: 'bold'}}>{t('job_lifecycle.content.current_status_accumulated_time')} ({lifecycleData.lifecycle[0].value}):</span> {lifecycleData.durations.totalCurrentStatusDuration.humanReadable}
</li>
}
</ul>
</Card>
: null}
</Card>
<Card type='inner' title={(
<>
<Space direction="horizontal" size="small">
<Badge status='processing' count={lifecycleData.lifecycle.length}/>
{t('job_lifecycle.content.title_transitions')}
</Space>
</>
)}>
<Table style={{
overflow: 'auto',
width: '100%',
}} columns={columns} dataSource={lifecycleData.lifecycle}/>
</Card>
</Space>
) : (
<Card type='inner' style={{textAlign: 'center'}}>
{t('job_lifecycle.content.data_unavailable')}
</Card>
)
) : (
<Card type='inner' title={t('job_lifecycle.content.title_loading')}>
{t('job_lifecycle.content.loading')}
</Card>
)}
</Card>
);
}
export default JobLifecycleComponent;

View File

@@ -1,149 +0,0 @@
import React, { useState } from "react";
import { useMutation } from "@apollo/client";
import { Button, Form, Popover, Select, Space, notification } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_LINE_BULK_ASSIGN } from "../../graphql/jobs-lines.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(mapStateToProps, mapDispatchToProps)(JoblineBulkAssign);
export function JoblineBulkAssign({
setSelectedLines,
selectedLines,
insertAuditTrail,
bodyshop,
jobRO,
job,
currentUser,
}) {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const { t } = useTranslation();
const [assignLines] = useMutation(UPDATE_LINE_BULK_ASSIGN);
const handleConvert = async (values) => {
try {
setLoading(true);
const result = await assignLines({
variables: {
jobline: {
assigned_team: values.assigned_team,
},
ids: selectedLines.map((l) => l.id),
},
});
if (result.errors) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: JSON.stringify(result.errors),
}),
});
} else {
//Insert the audit trail here.
const teamName = bodyshop.employee_teams.find(
(et) => et.id === values.assigned_team
)?.name;
const hours = selectedLines.reduce(
(acc, val) => (acc += val.mod_lb_hrs),
0
);
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.assignedlinehours(
teamName,
hours.toFixed(1)
),
});
setSelectedLines([]);
setVisible(false);
}
} catch (error) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: error,
}),
});
} finally {
setLoading(false);
}
};
const popMenu = (
<div>
<Form layout="vertical" form={form} onFinish={handleConvert}>
<Form.Item
name={"assigned_team"}
label={t("joblines.fields.assigned_team")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
showSearch
style={{ width: 200 }}
optionFilterProp="children"
filterOption={(input, option) =>
option.props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
{bodyshop.employee_teams.map((team) => (
<Select.Option value={team.id} key={team.id} name={team.name}>
{team.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Space wrap>
<Button type="danger" onClick={() => form.submit()} loading={loading}>
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisible(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</div>
);
return (
<Popover open={visible} content={popMenu}>
<Button
disabled={selectedLines.length === 0 || jobRO}
loading={loading}
onClick={() => setVisible(true)}
>
{t("joblines.actions.assign_team", { count: selectedLines.length })}
</Button>
</Popover>
);
}

View File

@@ -1,167 +0,0 @@
import React, { useState } from "react";
import { useMutation } from "@apollo/client";
import { Button, Form, Popover, Select, Space, notification } from "antd";
import moment from "moment";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_PARTS_DISPATCH } from "../../graphql/parts-dispatch.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobLineDispatchButton);
export function JobLineDispatchButton({
setSelectedLines,
selectedLines,
bodyshop,
jobRO,
job,
currentUser,
}) {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const Templates = TemplateList("job_special", {
ro_number: job.ro_number,
});
const { t } = useTranslation();
const [dispatchLines] = useMutation(INSERT_PARTS_DISPATCH);
const handleConvert = async (values) => {
try {
setLoading(true);
//THIS HAS NOT YET BEEN TESTED. START BY FINISHING THIS FUNCTION.
const result = await dispatchLines({
variables: {
partsDispatch: {
dispatched_at: moment(),
employeeid: values.employeeid,
jobid: job.id,
dispatched_by: currentUser.email,
parts_dispatch_lines: {
data: selectedLines.map((l) => ({
joblineid: l.id,
quantity: l.part_qty,
})),
},
},
//joblineids: selectedLines.map((l) => l.id),
},
});
if (result.errors) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: result.errors,
}),
});
} else {
setSelectedLines([]);
await GenerateDocument(
{
name: Templates.parts_dispatch.key,
variables: {
id: result.data.insert_parts_dispatch_one.id,
},
},
{},
"p"
);
}
setVisible(false);
} catch (error) {
notification.open({
type: "error",
message: t("parts_dispatch.errors.creating", {
error: error,
}),
});
} finally {
setLoading(false);
}
};
const popMenu = (
<div>
<Form layout="vertical" form={form} onFinish={handleConvert}>
<Form.Item
name={"employeeid"}
label={t("timetickets.fields.employee")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
showSearch
style={{ width: 200 }}
optionFilterProp="children"
filterOption={(input, option) =>
option.props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
{bodyshop.employees
.filter((emp) => emp.active)
.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
</Select>
</Form.Item>
<Space wrap>
<Button
type="danger"
onClick={() => form.submit()}
loading={loading}
disabled={selectedLines.length === 0}
>
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisible(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</div>
);
return (
<Popover open={visible} content={popMenu}>
<Button
disabled={selectedLines.length === 0 || jobRO}
loading={loading}
onClick={() => setVisible(true)}
>
{t("joblines.actions.dispatchparts", { count: selectedLines.length })}
</Button>
</Popover>
);
}

View File

@@ -1,5 +1,5 @@
import { useMutation } from "@apollo/client";
import { notification, Select, Space } from "antd";
import { notification, Select } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -77,10 +77,7 @@ export function JobLineLocationPopup({ bodyshop, jobline, disabled }) {
style={{ width: "100%", minHeight: "2rem", cursor: "pointer" }}
onClick={() => !disabled && setEditing(true)}
>
<Space wrap>
{jobline.location}
{jobline.parts_dispatch_lines?.length > 0 && "-Disp"}
</Space>
{jobline.location}
</div>
);
}

View File

@@ -1,115 +0,0 @@
import { notification, Select } from "antd";
import React, { useEffect, useState } from "react";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function JoblineTeamAssignment({
bodyshop,
jobline,
disabled,
jobId,
insertAuditTrail,
}) {
const [editing, setEditing] = useState(false);
const [loading, setLoading] = useState(false);
const [assignedTeam, setAssignedTeam] = useState(jobline.assigned_team);
const [updateJob] = useMutation(UPDATE_JOB_LINE);
const { t } = useTranslation();
useEffect(() => {
if (editing) setAssignedTeam(jobline.assigned_team);
}, [editing, jobline.assigned_team]);
const handleChange = (e) => {
setAssignedTeam(e);
};
const handleSave = async (e) => {
setLoading(true);
const result = await updateJob({
variables: {
lineId: jobline.id,
line: { assigned_team: assignedTeam },
},
});
if (!!!result.errors) {
notification["success"]({ message: t("joblines.successes.saved") });
//insert the audit trail here.
const teamName = bodyshop.employee_teams.find(
(et) => et.id === assignedTeam
)?.name;
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.assignedlinehours(
teamName,
jobline.mod_lb_hrs
),
});
} else {
notification["error"]({
message: t("joblines.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
setEditing(false);
};
if (editing)
return (
<div>
<LoadingSpinner loading={loading}>
<Select
autoFocus
allowClear
dropdownMatchSelectWidth={100}
value={assignedTeam}
onSelect={handleChange}
onBlur={handleSave}
onClear={() => handleChange(null)}
>
{Object.values(bodyshop.employee_teams).map((s, idx) => (
<Select.Option key={idx} value={s.id}>
{s.name}
</Select.Option>
))}
</Select>
</LoadingSpinner>
</div>
);
const team = bodyshop.employee_teams.find(
(tm) => tm.id === jobline.assigned_team
);
return (
<div
style={{ width: "100%", minHeight: "1rem", cursor: "pointer" }}
onClick={() => !disabled && setEditing(true)}
>
{team?.name}
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(JoblineTeamAssignment);

View File

@@ -1,18 +0,0 @@
import { Alert } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
export default function JobProfileDataWarning({ job }) {
const { t } = useTranslation();
let missingProfileInfo =
Object.keys(job.cieca_pft).length === 0 ||
Object.keys(job.cieca_pfl).length === 0 ||
Object.keys(job.materials).length === 0;
if (missingProfileInfo)
return (
<Alert type="error" message={t("jobs.labels.missingprofileinfo")}></Alert>
);
return null;
}

View File

@@ -19,8 +19,7 @@ export default function JobReconciliationModalComponent({ job, bills }) {
const jobLineData = job.joblines.filter(
(j) =>
(j.part_type !== "PAE" && j.act_price !== 0 && j.part_qty !== 0) ||
j.misc_amt !== 0 ||
(j.part_type !== null && j.part_type !== "PAE") ||
(j.line_desc &&
j.line_desc.toLowerCase().includes("towing") &&
j.lbr_op === "OP13") ||

View File

@@ -1,16 +1,16 @@
import { useMutation, useLazyQuery } from "@apollo/client";
import { CheckCircleOutlined } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client";
import {
Button,
Card,
Form,
InputNumber,
notification,
Popover,
Space,
notification,
} from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react";
import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
@@ -50,7 +50,6 @@ export default function ScoreboardAddButton({
const handleFinish = async (values) => {
logImEXEvent("job_close_add_to_scoreboard");
values.date = moment(values.date).format("YYYY-MM-DD");
setLoading(true);
let result;
@@ -178,7 +177,7 @@ export default function ScoreboardAddButton({
return acc + job.lbr_adjustments[val];
}, 0);
form.setFieldsValue({
date: moment(),
date: new moment(),
bodyhrs: Math.round(v.bodyhrs * 10) / 10,
painthrs: Math.round(v.painthrs * 10) / 10,
});

View File

@@ -1,31 +0,0 @@
import { Button, notification } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
export default function JobSendPartPriceChangeComponent({ job }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
try {
const ppcData = await axios.post("/job/ppc", { jobid: job.id });
await axios.post("http://localhost:1337/ppc/", ppcData.data);
} catch (error) {
notification.open({
type: "error",
message: t("jobs.errors.partspricechange", {
error: JSON.stringify(error),
}),
});
} finally {
setLoading(false);
}
};
return (
<Button onClick={handleClick} loading={loading}>
{t("jobs.actions.sendpartspricechange")}
</Button>
);
}

View File

@@ -67,8 +67,7 @@ export function JobsTotalsTableComponent({ jobRO, currentUser, job }) {
<JobTotalsTableTotals job={job} />
</Card>
</Col>
{(currentUser.email.includes("@imex.") ||
currentUser.email.includes("@rome.")) && (
{currentUser.email.includes("@imex.") && (
<Col span={24}>
<Card title="DEVELOPMENT USE ONLY">
<JobCalculateTotals job={job} disabled={jobRO} />

View File

@@ -123,10 +123,11 @@ export default function JobTotalsTableLabor({ job }) {
<Space>
{t("jobs.labels.mapa")}
{job.materials &&
job.materials.MAPA &&
job.materials.MAPA.cal_maxdlr !== undefined &&
job.materials.mapa &&
job.materials.mapa.cal_maxdlr &&
job.materials.mapa.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.MAPA.cal_maxdlr,
amount: job.materials.mapa.cal_maxdlr,
})}
</Space>
</Table.Summary.Cell>
@@ -147,10 +148,11 @@ export default function JobTotalsTableLabor({ job }) {
<Space wrap>
{t("jobs.labels.mash")}
{job.materials &&
job.materials.MASH &&
job.materials.MASH.cal_maxdlr !== undefined &&
job.materials.mash &&
job.materials.mash.cal_maxdlr &&
job.materials.mash.cal_maxdlr > 0 &&
t("jobs.labels.threshhold", {
amount: job.materials.MASH.cal_maxdlr,
amount: job.materials.mash.cal_maxdlr,
})}
</Space>
</Table.Summary.Cell>

View File

@@ -11,22 +11,6 @@ export default function JobTotalsTableParts({ job }) {
filteredInfo: {},
});
const insuranceAdjustments = useMemo(() => {
if (!job.job_totals) return [];
if (!job.job_totals?.parts?.adjustments) return [];
const adjs = [];
Object.keys(job.job_totals?.parts?.adjustments).forEach((key) => {
if (Dinero(job.job_totals?.parts?.adjustments[key]).getAmount() !== 0) {
adjs.push({
id: key,
amount: Dinero(job.job_totals.parts.adjustments[key]),
});
}
});
return adjs;
}, [job.job_totals]);
const data = useMemo(() => {
return Object.keys(job.job_totals.parts.parts.list)
.filter(
@@ -90,11 +74,11 @@ export default function JobTotalsTableParts({ job }) {
<Table.Summary.Cell>
{t("jobs.labels.prt_dsmk_total")}
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
{Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()}
</Table.Summary.Cell>
</Table.Summary.Row>
<Table.Summary.Row>
<Table.Summary.Cell>
<strong>{t("jobs.labels.partstotal")}</strong>
@@ -106,24 +90,6 @@ export default function JobTotalsTableParts({ job }) {
</strong>
</Table.Summary.Cell>
</Table.Summary.Row>
{insuranceAdjustments.length > 0 && (
<Table.Summary.Row>
<Table.Summary.Cell colSpan={24}>
{t("jobs.labels.profileadjustments")}
</Table.Summary.Cell>
</Table.Summary.Row>
)}
{insuranceAdjustments.map((adj, idx) => (
<Table.Summary.Row key={idx}>
<Table.Summary.Cell>
{t(`jobs.fields.${adj.id.toLowerCase()}`)}
</Table.Summary.Cell>
<Table.Summary.Cell align="right">
{adj.amount.toFormat()}
</Table.Summary.Cell>
</Table.Summary.Row>
))}
</>
)}
/>

View File

@@ -28,109 +28,26 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
total: job.job_totals.totals.subtotal,
bold: true,
},
...(job.job_totals.totals.us_sales_tax_breakdown
{
key: t("jobs.labels.local_tax_amt"),
total: job.job_totals.totals.local_tax,
},
{
key: t("jobs.labels.state_tax_amt"),
total: job.job_totals.totals.state_tax,
},
...(bodyshop.region_config === "CA_BC"
? [
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 ||
"T1"
} - ${[
job.cieca_pft.ty1_rate1,
job.cieca_pft.ty1_rate2,
job.cieca_pft.ty1_rate3,
job.cieca_pft.ty1_rate4,
job.cieca_pft.ty1_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax,
key: t("jobs.fields.ca_bc_pvrt"),
total: job.job_totals.additional.pvrt,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 ||
"T2"
} - ${[
job.cieca_pft.ty2_rate1,
job.cieca_pft.ty2_rate2,
job.cieca_pft.ty2_rate3,
job.cieca_pft.ty2_rate4,
job.cieca_pft.ty2_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 ||
"T3"
} - ${[
job.cieca_pft.ty3_rate1,
job.cieca_pft.ty3_rate2,
job.cieca_pft.ty3_rate3,
job.cieca_pft.ty3_rate4,
job.cieca_pft.ty3_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 ||
"T4"
} - ${[
job.cieca_pft.ty4_rate1,
job.cieca_pft.ty4_rate2,
job.cieca_pft.ty4_rate3,
job.cieca_pft.ty4_rate4,
job.cieca_pft.ty4_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax,
},
{
key: `${
bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 ||
"TT"
} - ${[
job.cieca_pft.ty5_rate1,
job.cieca_pft.ty5_rate2,
job.cieca_pft.ty5_rate3,
job.cieca_pft.ty5_rate4,
job.cieca_pft.ty5_rate5,
]
.filter((i) => i > 0)
.join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax,
},
{
key: t("jobs.labels.total_sales_tax"),
bold: true,
total: Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty1Tax)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty2Tax)
)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty3Tax)
)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty4Tax)
)
.add(
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty5Tax)
).toJSON(),
},
].filter((item) => item.total.amount !== 0)
: [
{
key: t("jobs.labels.state_tax_amt"),
total: job.job_totals.totals.state_tax,
},
]),
]
: []),
{
key: t("jobs.labels.federal_tax_amt"),
total: job.job_totals.totals.federal_tax,
},
{
key: t("jobs.labels.total_repairs"),
total: job.job_totals.totals.total_repairs,
@@ -140,10 +57,10 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
key: t("jobs.fields.ded_amt"),
total: job.job_totals.totals.custPayable.deductible,
},
// {
// key: t("jobs.fields.federal_tax_payable"),
// total: job.job_totals.totals.custPayable.federal_tax,
// },
{
key: t("jobs.fields.federal_tax_payable"),
total: job.job_totals.totals.custPayable.federal_tax,
},
{
key: t("jobs.fields.other_amount_payable"),
total: job.job_totals.totals.custPayable.other_customer_amount,
@@ -164,7 +81,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
bold: true,
},
];
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);
}, [job.job_totals, t, bodyshop.region_config]);
const columns = [
{

View File

@@ -53,14 +53,12 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
);
return (
<>
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus">
<Button shape="round">
<span>{job.status}</span>
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus">
<Button shape="round">
<span>{job.status}</span>
<DownCircleFilled />
</Button>
</Dropdown>
</>
<DownCircleFilled />
</Button>
</Dropdown>
);
}

View File

@@ -1,18 +1,34 @@
import { useMutation } from "@apollo/client";
import { Button, Space, notification } from "antd";
import { Button, notification } from "antd";
import { gql } from "@apollo/client";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import {
DELETE_DELIVERY_CHECKLIST,
DELETE_INTAKE_CHECKLIST,
} from "../../graphql/jobs.queries";
export default function JobAdminDeleteIntake({ job }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [deleteIntake] = useMutation(gql`
mutation DELETE_INTAKE($jobId: uuid!) {
update_jobs_by_pk(
pk_columns: { id: $jobId }
_set: { intakechecklist: null }
) {
id
intakechecklist
}
}
`);
const [deleteIntake] = useMutation(DELETE_INTAKE_CHECKLIST);
const [deleteDelivery] = useMutation(DELETE_DELIVERY_CHECKLIST);
const [DELETE_DELIVERY] = useMutation(gql`
mutation DELETE_DELIVERY($jobId: uuid!) {
update_jobs_by_pk(
pk_columns: { id: $jobId }
_set: { deliverchecklist: null }
) {
id
deliverchecklist
}
}
`);
const handleDelete = async (values) => {
setLoading(true);
@@ -34,7 +50,7 @@ export default function JobAdminDeleteIntake({ job }) {
const handleDeleteDelivery = async (values) => {
setLoading(true);
const result = await deleteDelivery({
const result = await DELETE_DELIVERY({
variables: { jobId: job.id },
});
@@ -52,22 +68,12 @@ export default function JobAdminDeleteIntake({ job }) {
return (
<>
<Space wrap>
<Button
loading={loading}
onClick={handleDelete}
disabled={!job.intakechecklist}
>
{t("jobs.labels.deleteintake")}
</Button>
<Button
loading={loading}
onClick={handleDeleteDelivery}
disabled={!job.deliverychecklist}
>
{t("jobs.labels.deletedelivery")}
</Button>
</Space>
<Button loading={loading} onClick={handleDelete}>
{t("jobs.labels.deleteintake")}
</Button>
<Button loading={loading} onClick={handleDeleteDelivery}>
{t("jobs.labels.deletedelivery")}
</Button>
</>
);
}

View File

@@ -1,5 +1,5 @@
import { useMutation } from "@apollo/client";
import { Button, Space, notification } from "antd";
import { gql, useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -7,11 +7,6 @@ import moment from "moment";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import {
MARK_JOB_AS_EXPORTED,
MARK_JOB_AS_UNINVOICED,
MARK_JOB_FOR_REEXPORT,
} from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import {
selectBodyshop,
@@ -40,18 +35,58 @@ export function JobAdminMarkReexport({
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
const [markJobForReexport] = useMutation(gql`
mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) {
update_jobs_by_pk(
pk_columns: { id: $jobId }
_set: { date_exported: null
status: "${bodyshop.md_ro_statuses.default_invoiced}"
}
) {
id
date_exported
status
date_invoiced
}
}
`);
const [markJobForReexport] = useMutation(MARK_JOB_FOR_REEXPORT);
const [markJobExported] = useMutation(MARK_JOB_AS_EXPORTED);
const [markJobUninvoiced] = useMutation(MARK_JOB_AS_UNINVOICED);
const [markJobExported] = useMutation(gql`
mutation MARK_JOB_AS_EXPORTED($jobId: uuid!, $date_exported: timestamptz!) {
update_jobs_by_pk(
pk_columns: { id: $jobId }
_set: { date_exported: $date_exported
status: "${bodyshop.md_ro_statuses.default_exported}"
}
) {
id
date_exported
date_invoiced
status
}
}
`);
const [markJobUninvoiced] = useMutation(gql`
mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, ) {
update_jobs_by_pk(
pk_columns: { id: $jobId }
_set: { date_exported: null
date_invoiced: null
status: "${bodyshop.md_ro_statuses.default_delivered}"
}
) {
id
date_exported
date_invoiced
status
}
}
`);
const handleMarkForExport = async () => {
setLoading(true);
const result = await markJobForReexport({
variables: {
jobId: job.id,
default_invoiced: bodyshop.md_ro_statuses.default_invoiced,
},
variables: { jobId: job.id },
});
if (!result.errors) {
@@ -73,11 +108,7 @@ export function JobAdminMarkReexport({
const handleMarkExported = async () => {
setLoading(true);
const result = await markJobExported({
variables: {
jobId: job.id,
date_exported: moment(),
default_exported: bodyshop.md_ro_statuses.default_exported,
},
variables: { jobId: job.id, date_exported: moment() },
});
await insertExportLog({
@@ -113,10 +144,7 @@ export function JobAdminMarkReexport({
const handleUninvoice = async () => {
setLoading(true);
const result = await markJobUninvoiced({
variables: {
jobId: job.id,
default_delivered: bodyshop.md_ro_statuses.default_delivered,
},
variables: { jobId: job.id },
});
if (!result.errors) {
@@ -137,29 +165,27 @@ export function JobAdminMarkReexport({
return (
<>
<Space wrap>
<Button
loading={loading}
disabled={!job.date_exported}
onClick={handleMarkForExport}
>
{t("jobs.labels.markforreexport")}
</Button>
<Button
loading={loading}
disabled={job.date_exported}
onClick={handleMarkExported}
>
{t("jobs.actions.markasexported")}
</Button>
<Button
loading={loading}
disabled={!job.date_invoiced || job.date_exported}
onClick={handleUninvoice}
>
{t("jobs.actions.uninvoice")}
</Button>
</Space>
<Button
loading={loading}
disabled={!job.date_exported}
onClick={handleMarkForExport}
>
{t("jobs.labels.markforreexport")}
</Button>
<Button
loading={loading}
disabled={job.date_exported}
onClick={handleMarkExported}
>
{t("jobs.actions.markasexported")}
</Button>
<Button
loading={loading}
disabled={!job.date_invoiced || job.date_exported}
onClick={handleUninvoice}
>
{t("jobs.actions.uninvoice")}
</Button>
</>
);
}

View File

@@ -1,65 +0,0 @@
import { useMutation } from "@apollo/client";
import { Switch, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_REMOVE_FROM_AR } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR);
export function JobsAdminRemoveAR({ insertAuditTrail, job }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [switchValue, setSwitchValue] = useState(job.remove_from_ar);
const [mutationUpdateRemoveFromAR] = useMutation(UPDATE_REMOVE_FROM_AR);
const handleChange = async (value) => {
setLoading(true);
const result = await mutationUpdateRemoveFromAR({
variables: { jobId: job.id, remove_from_ar: value },
});
if (!result.errors) {
notification["success"]({ message: t("jobs.successes.save") });
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.admin_job_remove_from_ar(value),
});
setSwitchValue(value);
} else {
notification["error"]({
message: t("jobs.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
};
return (
<>
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{ marginRight: "10px" }}>
{t("jobs.labels.remove_from_ar")}:
</div>
<div>
<Switch
checked={switchValue}
loading={loading}
onChange={handleChange}
/>
</div>
</div>
</>
);
}

View File

@@ -1,10 +1,9 @@
import { useMutation } from "@apollo/client";
import { gql, useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UNVOID_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import {
selectBodyshop,
@@ -30,17 +29,66 @@ export function JobsAdminUnvoid({
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [mutationUnvoidJob] = useMutation(UNVOID_JOB);
const [updateJob] = useMutation(gql`
mutation UNVOID_JOB($jobId: uuid!) {
update_jobs_by_pk(pk_columns: {id: $jobId}, _set: {voided: false, status: "${
bodyshop.md_ro_statuses.default_imported
}", date_void: null}) {
id
date_void
voided
status
}
insert_notes(objects: {jobid: $jobId, audit: true, created_by: "${
currentUser.email
}", text: "${t("jobs.labels.unvoidnote")}"}) {
returning {
id
}
}
}
`);
// const result = await voidJob({
// variables: {
// jobId: job.id,
// job: {
// status: bodyshop.md_ro_statuses.default_void,
// voided: true,
// },
// note: [
// {
// jobid: job.id,
// created_by: currentUser.email,
// audit: true,
// text: t("jobs.labels.voidnote", {
// date: moment().format("MM/DD/yyy"),
// time: moment().format("hh:mm a"),
// }),
// },
// ],
// },
// });
// if (!!!result.errors) {
// notification["success"]({
// message: t("jobs.successes.voided"),
// });
// //go back to jobs list.
// history.push(`/manage/`);
// } else {
// notification["error"]({
// message: t("jobs.errors.voiding", {
// error: JSON.stringify(result.errors),
// }),
// });
// }
const handleUpdate = async (values) => {
setLoading(true);
const result = await mutationUnvoidJob({
variables: {
jobId: job.id,
default_imported: bodyshop.md_ro_statuses.default_imported,
currentUserEmail: currentUser.email,
text: t("jobs.labels.unvoidnote"),
},
const result = await updateJob({
variables: { jobId: job.id },
});
if (!result.errors) {
@@ -62,10 +110,8 @@ export function JobsAdminUnvoid({
};
return (
<>
<Button loading={loading} disabled={!job.voided} onClick={handleUpdate}>
{t("jobs.actions.unvoid")}
</Button>
</>
<Button loading={loading} disabled={!job.voided} onClick={handleUpdate}>
{t("jobs.actions.unvoid")}
</Button>
);
}

View File

@@ -25,7 +25,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
//Found a relevant matching line. Add it to lines to update.
linesToUpdate.push({
id: existingLines[matchingIndex].id,
newData: { ...newLine, removed: false, act_price_before_ppc: null },
newData: { ...newLine, removed: false },
});
//Splice out item we found for performance.

View File

@@ -6,10 +6,10 @@ import {
useQuery,
} from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Button, Col, Row, notification } from "antd";
import { Col, notification, Row } from "antd";
import Axios from "axios";
import _ from "lodash";
import moment from "moment-business-days";
import Dinero from "dinero.js";
import moment from "moment";
import queryString from "query-string";
import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -30,8 +30,8 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
//import confirmDialog from "../../utils/asyncConfirm";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
@@ -73,15 +73,7 @@ export function JobsAvailableContainer({
const [selectedJob, setSelectedJob] = useState(null);
const [selectedOwner, setSelectedOwner] = useState(null);
const [partsQueueToggle, setPartsQueueToggle] = useState(
bodyshop.md_functionality_toggles.parts_queue_toggle
);
const [updateSchComp, setSchComp] = useState({
actual_in: moment(),
checked: false,
scheduled_completion: moment(),
automatic: false,
});
const [partsQueueToggle, setPartsQueueToggle] = useState(bodyshop.md_functionality_toggles.parts_queue_toggle);
const [insertLoading, setInsertLoading] = useState(false);
@@ -100,15 +92,14 @@ export function JobsAvailableContainer({
const modalSearchState = useState("");
//Import Scenario
const onOwnerFindModalOk = async (lazyData) => {
const onOwnerFindModalOk = async () => {
logImEXEvent("job_import_new");
setOwnerModalVisible(false);
setInsertLoading(true);
const estData = replaceEmpty(
lazyData?.available_jobs_by_pk || estDataRaw.data.available_jobs_by_pk
);
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
if (!(estData && estData.est_data)) {
//We don't have the right data. Error!
@@ -118,21 +109,17 @@ export function JobsAvailableContainer({
});
return;
}
// if (process.env.REACT_APP_COUNTRY === "USA") {
//Massage the CCC file set to remove duplicate UNQ_SEQ.
await ResolveCCCLineIssues(estData.est_data, bodyshop);
// } else {
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(estData.est_data, bodyshop);
// }
// const newTotals = (
// await Axios.post("/job/totals", {
// job: {
// ...estData.est_data,
// joblines: estData.est_data.joblines.data,
// },
// })
// ).data;
const newTotals = (
await Axios.post("/job/totals", {
job: {
...estData.est_data,
joblines: estData.est_data.joblines.data,
},
})
).data;
let existingVehicles;
if (estData.est_data.v_vin) {
@@ -147,9 +134,9 @@ export function JobsAvailableContainer({
const newJob = {
...estData.est_data,
// clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
// job_totals: newTotals,
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
job_totals: newTotals,
date_open: moment(),
status: bodyshop.md_ro_statuses.default_imported,
notes: {
@@ -173,23 +160,17 @@ export function JobsAvailableContainer({
delete newJob.vehicle;
}
if (typeof newJob.kmin === "string") {
newJob.kmin = null;
}
try {
const r = await insertNewJob({
variables: {
job: newJob,
},
});
await Axios.post("/job/totalsssu", {
id: r.data.insert_jobs.returning[0].id,
});
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
}
notification["success"]({
message: t("jobs.successes.created"),
onClick: () => {
@@ -203,7 +184,7 @@ export function JobsAvailableContainer({
operation: AuditTrailMapping.jobimported(),
});
await deleteJob({
deleteJob({
variables: { id: estData.id },
}).then((r) => {
refetch();
@@ -211,22 +192,16 @@ export function JobsAvailableContainer({
});
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
} catch (r) {
} catch (err) {
//error while inserting
notification["error"]({
message: t("jobs.errors.creating", { error: r.message }),
});
refetch().catch((err) => {
console.error(
`Something went wrong in jobs available table container - ${
err.message || ""
}`
);
message: t("jobs.errors.creating", { error: err.message }),
});
refetch().catch(e => {console.error(`Something went wrong in jobs available table container - ${err.message || ''}`)});
setInsertLoading(false);
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
}
};
//Supplement scenario
@@ -249,23 +224,6 @@ export function JobsAvailableContainer({
let supp = replaceEmpty({ ...estData.est_data });
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(supp, bodyshop);
await ResolveCCCLineIssues(supp, bodyshop);
if (updateSchComp.checked === true) {
if (updateSchComp.automatic === true) {
const job_hrs = supp.joblines.data.reduce(
(acc, val) => acc + val.mod_lb_hrs,
0
);
const num_days = job_hrs / bodyshop.target_touchtime;
supp.actual_in = updateSchComp.actual_in;
supp.scheduled_completion = moment(
updateSchComp.actual_in
).businessAdd(num_days, "days");
} else {
supp.scheduled_completion = updateSchComp.scheduled_completion;
}
}
delete supp.owner;
delete supp.vehicle;
@@ -303,9 +261,9 @@ export function JobsAvailableContainer({
},
});
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
if (CriticalPartsScanning.treatment === "on") {
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
}
if (updateResult.errors) {
@@ -409,6 +367,7 @@ export function JobsAvailableContainer({
if (error) return <AlertComponent type="error" message={error.message} />;
return (
<LoadingSpinner
loading={insertLoading}
@@ -425,6 +384,7 @@ export function JobsAvailableContainer({
visible={ownerModalVisible}
onOk={onOwnerFindModalOk}
onCancel={onOwnerModalCancel}
/>
<JobsFindModalContainer
loading={estDataRaw.loading}
@@ -438,28 +398,7 @@ export function JobsAvailableContainer({
modalSearchState={modalSearchState}
partsQueueToggle={partsQueueToggle}
setPartsQueueToggle={setPartsQueueToggle}
updateSchComp={updateSchComp}
setSchComp={setSchComp}
/>
{currentUser.email.includes("@rome.") ||
currentUser.email.includes("@imex.") ? (
<Button
onClick={async () => {
for (const record of data.available_jobs) {
//Query the data
console.log("Start Job", record.id);
const { data } = await loadEstData({
variables: { id: record.id },
});
console.log("Query has been awaited and is complete");
await onOwnerFindModalOk(data);
}
}}
>
Add all jobs as new.
</Button>
) : null}
<Row gutter={[16, 16]}>
<Col span={24}>
<JobsAvailableTableComponent
@@ -491,158 +430,115 @@ function replaceEmpty(someObj, replaceValue = null) {
}
async function CheckTaxRates(estData, bodyshop) {
// //LKQ Check
// if (
// !estData.parts_tax_rates?.PAL ||
// estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
// estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
// ) {
// const res = await confirmDialog(
// `Rome Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
// );
// if (res) {
// if (!estData.parts_tax_rates.PAL) {
// estData.parts_tax_rates.PAL = {
// prt_discp: 0,
// prt_mktyp: true,
// prt_mkupp: 0,
// prt_type: "PAL",
// };
// }
// estData.parts_tax_rates.PAL.prt_tax_rt =
// bodyshop.bill_tax_rates.state_tax_rate / 100;
// estData.parts_tax_rates.PAL.prt_tax_in = true;
// }
// }
// //PAC Check
// if (
// !estData.parts_tax_rates?.PAC ||
// estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
// estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
// ) {
// const res = await confirmDialog(
// `Rome Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
// );
// if (res) {
// if (!estData.parts_tax_rates.PAC) {
// estData.parts_tax_rates.PAC = {
// prt_discp: 0,
// prt_mktyp: true,
// prt_mkupp: 0,
// prt_type: "PAC",
// };
// }
// estData.parts_tax_rates.PAC.prt_tax_rt =
// bodyshop.bill_tax_rates.state_tax_rate / 100;
// estData.parts_tax_rates.PAC.prt_tax_in = true;
// }
// }
//LKQ Check
if (
!estData.parts_tax_rates?.PAL ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAL) {
estData.parts_tax_rates.PAL = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAL",
};
}
estData.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAL.prt_tax_in = true;
}
}
//PAC Check
if (
!estData.parts_tax_rates?.PAC ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAC) {
estData.parts_tax_rates.PAC = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAC",
};
}
estData.parts_tax_rates.PAC.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAC.prt_tax_in = true;
}
}
//PAM Check
if (!estData.parts_tax_rates?.PAM) {
estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC;
if (
!estData.parts_tax_rates?.PAM ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAM) {
estData.parts_tax_rates.PAM = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAM",
};
}
estData.parts_tax_rates.PAM.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAM.prt_tax_in = true;
}
}
// //PAM Check
// if (
// !estData.parts_tax_rates?.PAM ||
// estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
// estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
// ) {
// const res = await confirmDialog(
// `Rome Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
// );
// if (res) {
// if (!estData.parts_tax_rates.PAM) {
// estData.parts_tax_rates.PAM = {
// prt_discp: 0,
// prt_mktyp: true,
// prt_mkupp: 0,
// prt_type: "PAM",
// };
// }
// estData.parts_tax_rates.PAM.prt_tax_rt =
// bodyshop.bill_tax_rates.state_tax_rate / 100;
// estData.parts_tax_rates.PAM.prt_tax_in = true;
// }
// }
// if (
// !estData.parts_tax_rates?.PAR ||
// estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
// estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
// ) {
// const res = await confirmDialog(
// `Rome Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
// );
// if (res) {
// if (!estData.parts_tax_rates.PAR) {
// estData.parts_tax_rates.PAR = {
// prt_discp: 0,
// prt_mktyp: true,
// prt_mkupp: 0,
// prt_type: "PAR",
// };
// }
// estData.parts_tax_rates.PAR.prt_tax_rt =
// bodyshop.bill_tax_rates.state_tax_rate / 100;
// estData.parts_tax_rates.PAR.prt_tax_in = true;
// }
// }
if (
!estData.parts_tax_rates?.PAR ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAR) {
estData.parts_tax_rates.PAR = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAR",
};
}
estData.parts_tax_rates.PAR.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAR.prt_tax_in = true;
}
}
//IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate.
//Currently limited to SK shops only.
if (bodyshop.region_config === "CA_SK") {
estData.joblines.data.forEach((jl, index) => {
if (
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
jl.lbr_op !== "OP11"
) {
estData.joblines.data[index].tax_part = jl.lbr_tax;
}
//if (bodyshop.region_config === "CA_SK") {
estData.joblines.data.forEach((jl, index) => {
if (
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
jl.lbr_op !== "OP11"
) {
estData.joblines.data[index].tax_part = jl.lbr_tax;
}
//Set markup lines and tax lines as taxable.
//900510 is a mark up. 900510 is a discount.
if (jl.db_ref === "900510") {
estData.joblines.data[index].tax_part = true;
}
});
}
}
async function ResolveCCCLineIssues(estData, bodyshop) {
//Find all misc amounts, populate them to the act price.
//TODO Ensure that this doesnt get violated
//This needs to be done before cleansing unq_seq since some misc prices could move over.
estData.joblines.data.forEach((line) => {
if (line.misc_amt && line.misc_amt !== 0) {
line.act_price = line.act_price + line.misc_amt;
line.tax_part = !!line.misc_tax;
//Set markup lines and tax lines as taxable.
//900510 is a mark up. 900510 is a discount.
if (jl.db_ref === "900510") {
estData.joblines.data[index].tax_part = true;
}
});
//Generate the list of duplicated UNQ_SEQ that will feed into the next section to scrub the lines.
const unqSeqHash = _.groupBy(estData.joblines.data, "unq_seq");
const duplicatedUnqSeq = Object.keys(unqSeqHash).filter(
(key) => unqSeqHash[key].length > 1
);
duplicatedUnqSeq.forEach((unq_seq) => {
//Keys are strings, convert to int.
const int_unq_seq = parseInt(unq_seq);
//When line splitting, the first line is always the non-refinish line. We will keep it as is.
//We will cleanse the second line, which is always the next line.
const nonRefLineIndex = estData.joblines.data.findIndex(
(line) => line.unq_seq === int_unq_seq
);
estData.joblines.data[nonRefLineIndex + 1] = {
...estData.joblines.data[nonRefLineIndex + 1],
part_type: null,
act_price: 0,
db_price: 0,
prt_dsmk_p: 0,
prt_dsmk_m: 0,
};
});
//}
}

View File

@@ -36,8 +36,6 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
ret.profitcenter_part = defaults.profits["MAPA"];
} else if (lineDesc.includes("ats amount")) {
ret.profitcenter_part = defaults.profits["ATS"];
} else if (jl.act_price > 0) {
ret.profitcenter_part = defaults.profits["PAO"];
} else {
ret.profitcenter_part = null;
}

View File

@@ -9,12 +9,10 @@ import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({
@@ -22,11 +20,6 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
function updateJobCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
@@ -47,7 +40,6 @@ export function JobsCloseExportButton({
disabled,
setSelectedJobs,
refetch,
insertAuditTrail,
}) {
const history = useHistory();
const { t } = useTranslation();
@@ -189,10 +181,6 @@ export function JobsCloseExportButton({
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.jobexported(),
});
updateJobCache(
jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)
);
@@ -204,20 +192,12 @@ export function JobsCloseExportButton({
});
}
}
if (
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
successfulTransactions.length > 0
) {
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.jobexported(),
});
updateJobCache([
...new Set(
successfulTransactions.map(
@@ -247,7 +227,4 @@ export function JobsCloseExportButton({
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsCloseExportButton);
export default connect(mapStateToProps, null)(JobsCloseExportButton);

View File

@@ -1,4 +1,4 @@
import { Collapse, Form, Input, Select, Switch } from "antd";
import { Collapse, Form, Input, InputNumber, Select, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -12,12 +12,6 @@ import FormItemPhone, {
} from "../form-items-formatted/phone-form-item.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component";
import JobsDetailRatesLabor from "../jobs-detail-rates/jobs-detail-rates.labor.component";
import JobsDetailRatesMaterials from "../jobs-detail-rates/jobs-detail-rates.materials.component";
import JobsDetailRatesOther from "../jobs-detail-rates/jobs-detail-rates.other.component";
import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.component";
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
@@ -264,28 +258,26 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
<CurrencyInput />
</Form.Item>
</LayoutFormRow>
{
// <LayoutFormRow>
// <Form.Item
// label={t("jobs.fields.federal_tax_rate")}
// name="federal_tax_rate"
// >
// <InputNumber min={0} max={1} precision={2} />
// </Form.Item>
// <Form.Item
// label={t("jobs.fields.state_tax_rate")}
// name="state_tax_rate"
// >
// <InputNumber min={0} max={1} precision={2} />
// </Form.Item>
// <Form.Item
// label={t("jobs.fields.local_tax_rate")}
// name="local_tax_rate"
// >
// <InputNumber min={0} max={1} precision={2} />
// </Form.Item>
// </LayoutFormRow>
}
<LayoutFormRow>
<Form.Item
label={t("jobs.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<InputNumber min={0} max={1} precision={2} />
</Form.Item>
<Form.Item
label={t("jobs.fields.state_tax_rate")}
name="state_tax_rate"
>
<InputNumber min={0} max={1} precision={2} />
</Form.Item>
<Form.Item
label={t("jobs.fields.local_tax_rate")}
name="local_tax_rate"
>
<InputNumber min={0} max={1} precision={2} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow>
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
<CurrencyInput />
@@ -364,10 +356,6 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
required={selected && true}
form={form}
/>
<JobsDetailRatesLabor form={form} />
<JobsDetailRatesMaterials form={form} />
<JobsDetailRatesOther form={form} />
<JobsDetailRatesTaxes form={form} />
</div>
);
}

View File

@@ -19,13 +19,10 @@ export default function JobsCreateOwnerInfoNewComponent() {
label={t("owners.fields.ownr_ln")}
name={["owner", "data", "ownr_ln"]}
rules={[
({ getFieldValue }) => ({
required:
state.owner.new &&
(!getFieldValue(["owner", "data", "ownr_co_nm"]) ||
getFieldValue(["owner", "data", "ownr_co_nm"]) === ""),
{
required: state.owner.new,
//message: t("general.validation.required"),
}),
},
]}
>
<Input disabled={!state.owner.new} />
@@ -34,13 +31,10 @@ export default function JobsCreateOwnerInfoNewComponent() {
label={t("owners.fields.ownr_fn")}
name={["owner", "data", "ownr_fn"]}
rules={[
({ getFieldValue }) => ({
required:
state.owner.new &&
(!getFieldValue(["owner", "data", "ownr_co_nm"]) ||
getFieldValue(["owner", "data", "ownr_co_nm"]) === ""),
{
required: state.owner.new,
//message: t("general.validation.required"),
}),
},
]}
>
<Input disabled={!state.owner.new} />
@@ -57,17 +51,6 @@ export default function JobsCreateOwnerInfoNewComponent() {
<Form.Item
label={t("owners.fields.ownr_co_nm")}
name={["owner", "data", "ownr_co_nm"]}
rules={[
({ getFieldValue }) => ({
required:
state.owner.new &&
(!getFieldValue(["owner", "data", "ownr_ln"]) ||
!getFieldValue(["owner", "data", "ownr_fn"]) ||
getFieldValue(["owner", "data", "ownr_ln"]) === "" ||
getFieldValue(["owner", "data", "ownr_fn"]) === ""),
//message: t("general.validation.required"),
}),
]}
>
<Input disabled={!state.owner.new} />
</Form.Item>

View File

@@ -50,8 +50,6 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setModalContext({ context: context, modal: "jobCosting" })),
setTimeTicketContext: (context) =>
dispatch(setModalContext({ context: context, modal: "timeTicket" })),
setTimeTicketTaskContext: (context) =>
dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
setCardPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
insertAuditTrail: ({ jobid, operation }) =>
@@ -69,7 +67,6 @@ export function JobsDetailHeaderActions({
setJobCostingContext,
jobRO,
setTimeTicketContext,
setTimeTicketTaskContext,
setCardPaymentContext,
insertAuditTrail,
}) {
@@ -111,14 +108,6 @@ export function JobsDetailHeaderActions({
},
},
});
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.alertToggle(
!!job.production_vars && !!job.production_vars.alert
? !job.production_vars.alert
: true
),
});
};
const handleSuspend = (e) => {
@@ -132,12 +121,6 @@ export function JobsDetailHeaderActions({
},
},
});
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobsuspend(
!!job.suspended ? !job.suspended : true
),
});
};
const statusmenu = (
@@ -285,24 +268,6 @@ export function JobsDetailHeaderActions({
>
{t("timetickets.actions.enter")}
</Menu.Item>
{bodyshop.md_tasks_presets.enable_tasks && (
<Menu.Item
key="claimtimetickettasks"
disabled={
!job.converted ||
(!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced)
}
onClick={() => {
setTimeTicketTaskContext({
actions: {},
context: { jobid: job.id },
});
}}
>
{t("timetickets.actions.claimtasks")}
</Menu.Item>
)}
<Menu.Item
key="enterpayments"
disabled={!job.converted}
@@ -567,10 +532,6 @@ export function JobsDetailHeaderActions({
notification["success"]({
message: t("jobs.successes.voided"),
});
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobvoid(),
});
//go back to jobs list.
history.push(`/manage/`);
} else {

View File

@@ -1,8 +1,8 @@
import {
BranchesOutlined,
ExclamationCircleFilled,
PauseCircleOutlined,
WarningFilled,
BranchesOutlined,
} from "@ant-design/icons";
import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
import React, { useState } from "react";
@@ -15,7 +15,6 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component";
import JobAltTransportChange from "../job-at-change/job-at-change.component";
@@ -123,23 +122,20 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
</DataLabel>
{job.cccontracts.length > 0 && (
<DataLabel label={t("jobs.labels.contracts")}>
{job.cccontracts.map((c, index) => (
<Space wrap>
<Link
key={c.id}
to={`/manage/courtesycars/contracts/${c.id}`}
>
{`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`}
{index !== job.cccontracts.length - 1 ? "," : null}
</Link>
</Space>
{job.cccontracts.map((c) => (
<Link
key={c.id}
to={`/manage/courtesycars/contracts/${c.id}`}
>{`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`}</Link>
))}
</DataLabel>
)}
<DataLabel label={t("jobs.fields.production_vars.note")}>
<ProductionListColumnProductionNote record={job} />
</DataLabel>
<Space wrap>
<Space>
{job.special_coverage_policy && (
<Tag color="tomato">
<Space>
@@ -164,35 +160,19 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<Card
style={{ height: "100%" }}
title={
disabled ? (
<>
{ownerTitle.length > 0
? ownerTitle
: t("owner.labels.noownerinfo")}
</>
) : (
<Link to={`/manage/owners/${job.owner.id}`}>
{ownerTitle.length > 0
? ownerTitle
: t("owner.labels.noownerinfo")}
</Link>
)
<Link to={disabled ? "#" : `/manage/owners/${job.owner.id}`}>
{ownerTitle.length > 0
? ownerTitle
: t("owner.labels.noownerinfo")}
</Link>
}
>
<div>
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
{disabled ? (
<PhoneNumberFormatter>{job.ownr_ph1}</PhoneNumberFormatter>
) : (
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
)}
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
</DataLabel>
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
{disabled ? (
<PhoneNumberFormatter>{job.ownr_ph2}</PhoneNumberFormatter>
) : (
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
)}
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
</DataLabel>
<DataLabel key="3" label={t("owners.fields.address")}>
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
@@ -200,11 +180,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
} ${job.ownr_st || ""} ${job.ownr_zip || ""}`}
</DataLabel>
<DataLabel key="4" label={t("owners.fields.ownr_ea")}>
{disabled ? (
<>{job.ownr_ea || ""}</>
) : job.ownr_ea ? (
<a href={`mailto:${job.ownr_ea}`}>{job.ownr_ea}</a>
) : null}
{job.ownr_ea || ""}
</DataLabel>
{job.owner?.tax_number && (
<DataLabel key="5" label={t("owners.fields.tax_number")}>
@@ -219,19 +195,17 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
style={{ height: "100%" }}
title={
job.vehicle ? (
disabled ? (
<>
{vehicleTitle.length > 0
? vehicleTitle
: t("vehicles.labels.novehinfo")}{" "}
</>
) : (
<Link to={job.vehicle && `/manage/vehicles/${job.vehicle.id}`}>
{vehicleTitle.length > 0
? vehicleTitle
: t("vehicles.labels.novehinfo")}
</Link>
)
<Link
to={
disabled
? "#"
: job.vehicle && `/manage/vehicles/${job.vehicle.id}`
}
>
{vehicleTitle.length > 0
? vehicleTitle
: t("vehicles.labels.novehinfo")}
</Link>
) : (
<span></span>
)
@@ -247,19 +221,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<VehicleVinDisplay>
{`${job.v_vin || t("general.labels.na")}`}
</VehicleVinDisplay>
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
job.v_vin?.length !== 17 ? (
<WarningFilled
style={{ color: "tomato", marginLeft: ".3rem" }}
/>
) : null
) : null}
</DataLabel>
<DataLabel label={t("jobs.fields.regie_number")}>
{job.regie_number || t("general.labels.na")}
</DataLabel>
<DataLabel label={t("jobs.labels.relatedros")}>
<JobsRelatedRos jobid={job.id} job={job} disabled={disabled} />
<JobsRelatedRos jobid={job.id} job={job} />
</DataLabel>
{job.vehicle && job.vehicle.notes && (
<DataLabel

View File

@@ -5,13 +5,9 @@ import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import LaborAllocationsTableComponent from "../labor-allocations-table/labor-allocations-table.component";
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
import PayrollLaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.payroll.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
});
export default connect(mapStateToProps, null)(JobsDetailLaborContainer);
@@ -52,7 +48,6 @@ const adjSpan = {
};
export function JobsDetailLaborContainer({
bodyshop,
jobRO,
job,
jobId,
@@ -63,12 +58,6 @@ export function JobsDetailLaborContainer({
techConsole,
adjustments,
}) {
const { Enhanced_Payroll } = useTreatments(
["Enhanced_Payroll"],
{},
bodyshop.imexshopid
);
return (
<Row gutter={[16, 16]}>
<Col {...ticketSpan}>
@@ -81,28 +70,14 @@ export function JobsDetailLaborContainer({
jobId={jobId}
/>
</Col>
{Enhanced_Payroll.treatment === "on" ? (
<Col {...adjSpan}>
<PayrollLaborAllocationsTable
jobId={jobId}
joblines={joblines}
timetickets={timetickets}
refetch={refetch}
adjustments={adjustments}
/>
</Col>
) : (
<Col {...adjSpan}>
<LaborAllocationsTableComponent
jobId={jobId}
joblines={joblines}
timetickets={timetickets}
refetch={refetch}
adjustments={adjustments}
/>
</Col>
)}
<Col {...adjSpan}>
<LaborAllocationsTableComponent
jobId={jobId}
joblines={joblines}
timetickets={timetickets}
adjustments={adjustments}
/>
</Col>
</Row>
);
}

View File

@@ -6,14 +6,12 @@ import BillsListTable from "../bills-list-table/bills-list-table.component";
import JobBillsTotal from "../job-bills-total/job-bills-total.component";
import PartsOrderListTableComponent from "../parts-order-list-table/parts-order-list-table.component";
import PartsOrderModal from "../parts-order-modal/parts-order-modal.container";
import PartsDispatchTable from "../parts-dispatch-table/parts-dispatch-table.component";
export default function JobsDetailPliComponent({
job,
billsQuery,
handleBillOnRowClick,
handlePartsOrderOnRowClick,
handlePartsDispatchOnRowClick,
}) {
return (
<div>
@@ -45,13 +43,6 @@ export default function JobsDetailPliComponent({
billsQuery={billsQuery}
/>
</Col>
<Col span={24}>
<PartsDispatchTable
job={job}
handleOnRowClick={handlePartsDispatchOnRowClick}
billsQuery={billsQuery}
/>
</Col>
</Row>
</div>
);

View File

@@ -39,24 +39,12 @@ export default function JobsDetailPliContainer({ job }) {
}
};
const handlePartsDispatchOnRowClick = (record) => {
if (record) {
if (record.id) {
search.partsdispatchid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.partsdispatchid;
history.push({ search: queryString.stringify(search) });
}
};
return (
<JobsDetailPliComponent
job={job}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
handlePartsDispatchOnRowClick={handlePartsDispatchOnRowClick}
/>
);
}

View File

@@ -1,21 +1,25 @@
import { Divider, Form, Input, Select, Space, Switch, Tooltip } from "antd";
import {
Divider,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
Tooltip,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CABCpvrtCalculator from "../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import FormRow from "../layout-form-row/layout-form-row.component";
import JobsDetailRatesLabor from "./jobs-detail-rates.labor.component";
import JobsDetailRatesMaterials from "./jobs-detail-rates.materials.component";
import JobsDetailRatesOther from "./jobs-detail-rates.other.component";
import JobsDetailRatesParts from "./jobs-detail-rates.parts.component";
import JobsDetailRatesTaxes from "./jobs-detail-rates.taxes.component";
import JobsDetailRatesProfileOVerride from "./jobs-detail-rates.profile-override.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -80,7 +84,14 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
>
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
</Form.Item>
{bodyshop.region_config === "CA_BC" && (
<Space align="center">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} />
</Space>
)}
<Form.Item
label={t("jobs.fields.auto_add_ats")}
name="auto_add_ats"
@@ -109,7 +120,41 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
}}
</Form.Item>
</FormRow>
<FormRow>
<Form.Item
label={t("jobs.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.state_tax_rate")}
name="state_tax_rate"
>
<InputNumber
min={0}
max={1}
precision={2}
disabled={jobRO}
autoComplete="new-password"
/>
</Form.Item>
<Form.Item
label={t("jobs.fields.local_tax_rate")}
name="local_tax_rate"
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
)}
</FormRow>
<Divider
orientation="left"
type="horizontal"
@@ -197,15 +242,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
</FormRow>
<Divider orientation="left">Tax Profile</Divider>
<JobsDetailRatesProfileOVerride form={form} />
<JobsDetailRatesParts form={form} />
<JobsDetailRatesLabor form={form} />
<JobsDetailRatesMaterials form={form} />
<JobsDetailRatesOther form={form} />
<JobsDetailRatesTaxes form={form} />
</div>
);
}

View File

@@ -1,427 +0,0 @@
import { Collapse, Form, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
});
export function JobsDetailRatesLabor({
jobRO,
expanded,
required = true,
form,
}) {
const { t } = useTranslation();
return (
<Collapse defaultActiveKey={expanded && "rates"}>
<Collapse.Panel
forceRender
header={t("jobs.labels.cieca_pfl")}
key="cieca_pfl"
>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAB")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAB", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAB", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAB", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAB", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAB", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAB", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAD")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAD", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAD", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAD", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAD", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAD", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAD", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAE")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAE", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAE", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAE", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAE", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAE", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAE", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAF")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAF", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAF", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAF", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAF", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAF", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAF", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAG")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAG", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAG", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAG", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAG", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAG", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAG", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAM")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAM", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAM", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAM", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAM", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAM", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAM", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAR")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAR", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAR", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAR", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAR", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAR", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAR", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAS")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAS", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAS", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAS", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAS", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAS", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAS", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.lbr_types.LAU")}>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
name={["cieca_pfl", "LAU", "lbr_tax_in"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
name={["cieca_pfl", "LAU", "lbr_tx_in1"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
name={["cieca_pfl", "LAU", "lbr_tx_in2"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
name={["cieca_pfl", "LAU", "lbr_tx_in3"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
name={["cieca_pfl", "LAU", "lbr_tx_in4"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
name={["cieca_pfl", "LAU", "lbr_tx_in5"]}
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
</Collapse.Panel>
</Collapse>
);
}
export default connect(mapStateToProps, null)(JobsDetailRatesLabor);

View File

@@ -1,145 +0,0 @@
import { Collapse, Form, Input, InputNumber, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
});
export function JobsDetailRatesMaterials({
jobRO,
expanded,
required = true,
form,
}) {
const { t } = useTranslation();
return (
<Collapse defaultActiveKey={expanded && "rates"}>
<Collapse.Panel
forceRender
header={t("jobs.fields.materials.materials")}
key="materials"
>
<LayoutFormRow header={t("jobs.fields.materials.MAPA")}>
<Form.Item
label={t("jobs.fields.materials.cal_maxdlr")}
name={["materials", "MAPA", "cal_maxdlr"]}
>
<InputNumber min={0} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.cal_opcode")}
name={["materials", "MAPA", "cal_opcode"]}
>
<Input disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.tax_ind")}
name={["materials", "MAPA", "tax_ind"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in1")}
name={["materials", "MAPA", "mat_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in2")}
name={["materials", "MAPA", "mat_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in3")}
name={["materials", "MAPA", "mat_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in4")}
name={["materials", "MAPA", "mat_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in5")}
name={["materials", "MAPA", "mat_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("jobs.fields.materials.MASH")}>
<Form.Item
label={t("jobs.fields.materials.cal_maxdlr")}
name={["materials", "MASH", "cal_maxdlr"]}
>
<InputNumber min={0} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.cal_opcode")}
name={["materials", "MASH", "cal_opcode"]}
>
<Input disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.tax_ind")}
name={["materials", "MASH", "tax_ind"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in1")}
name={["materials", "MASH", "mat_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in2")}
name={["materials", "MASH", "mat_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in3")}
name={["materials", "MASH", "mat_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in4")}
name={["materials", "MASH", "mat_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.materials.mat_tx_in5")}
name={["materials", "MASH", "mat_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
</Collapse.Panel>
</Collapse>
);
}
export default connect(mapStateToProps, null)(JobsDetailRatesMaterials);

View File

@@ -1,104 +0,0 @@
import { Collapse, Form, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
});
export function JobsDetailRatesOther({
jobRO,
expanded,
required = true,
form,
}) {
const { t } = useTranslation();
return (
<Collapse defaultActiveKey={expanded && "rates"}>
<Collapse.Panel
forceRender
header={t("jobs.labels.cieca_pfo")}
key="cieca_pfo"
>
<LayoutFormRow noDivider>
<Form.Item
label={t("jobs.fields.cieca_pfo.tow_t_in1")}
name={["cieca_pfo", "tow_t_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.tow_t_in2")}
name={["cieca_pfo", "tow_t_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.tow_t_in3")}
name={["cieca_pfo", "tow_t_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.tow_t_in4")}
name={["cieca_pfo", "tow_t_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.tow_t_in5")}
name={["cieca_pfo", "tow_t_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.stor_t_in1")}
name={["cieca_pfo", "stor_t_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.stor_t_in2")}
name={["cieca_pfo", "stor_t_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.stor_t_in3")}
name={["cieca_pfo", "stor_t_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.stor_t_in4")}
name={["cieca_pfo", "stor_t_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.cieca_pfo.stor_t_in5")}
name={["cieca_pfo", "stor_t_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
</Collapse.Panel>
</Collapse>
);
}
export default connect(mapStateToProps, null)(JobsDetailRatesOther);

View File

@@ -29,7 +29,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAA", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -42,7 +42,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAA", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -68,58 +68,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAA", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAA", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAA", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAA", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAA", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAC")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAC", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -132,7 +92,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAC", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -158,58 +118,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAC", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAC", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAC", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAC", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAC", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAL")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAL", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -222,7 +142,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAL", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -248,58 +168,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAL", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAL", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAL", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAL", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAL", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAG")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAG", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -312,7 +192,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAG", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -338,58 +218,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAG", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAG", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAG", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAG", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAG", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAM")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAM", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -402,7 +242,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAM", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -428,58 +268,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAM", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAM", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAM", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAM", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAM", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAN")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAN", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -492,7 +292,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAN", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -518,58 +318,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAN", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAN", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAN", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAN", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAN", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAO")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAO", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -582,7 +342,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAO", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -608,58 +368,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAO", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAO", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAO", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAO", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAO", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAP")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAP", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -672,7 +392,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAP", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -698,58 +418,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAP", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAP", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAP", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAP", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAP", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAR")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAR", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -762,7 +442,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAR", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -788,58 +468,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAR", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAR", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAR", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAR", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAR", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PAS")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PAS", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -852,7 +492,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PAS", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -878,58 +518,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PAS", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PAS", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PAS", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PAS", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PAS", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.PASL")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "PASL", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -942,7 +542,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "PASL", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -968,58 +568,18 @@ export function JobsDetailRatesParts({
},
]}
>
<InputNumber
min={0}
max={100}
precision={4}
disabled={jobRO}
/>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
name={["parts_tax_rates", "PASL", "prt_tx_in1"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
name={["parts_tax_rates", "PASL", "prt_tx_in2"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
name={["parts_tax_rates", "PASL", "prt_tx_in3"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
name={["parts_tax_rates", "PASL", "prt_tx_in4"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
name={["parts_tax_rates", "PASL", "prt_tx_in5"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.CCDR")}>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "CCDR", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -1032,7 +592,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "CCDR", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -1045,7 +605,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
name={["parts_tax_rates", "CCDR", "prt_tax_rt"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.CCF")}>
@@ -1053,7 +613,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "CCF", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -1066,7 +626,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "CCF", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -1079,7 +639,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
name={["parts_tax_rates", "CCF", "prt_tax_rt"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.CCM")}>
@@ -1087,7 +647,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "CCM", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -1100,7 +660,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "CCM", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -1113,7 +673,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
name={["parts_tax_rates", "CCM", "prt_tax_rt"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.CCC")}>
@@ -1121,7 +681,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "CCC", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -1134,7 +694,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "CCC", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -1147,7 +707,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
name={["parts_tax_rates", "CCC", "prt_tax_rt"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("joblines.fields.part_types.CCD")}>
@@ -1155,7 +715,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_discp")}
name={["parts_tax_rates", "CCD", "prt_discp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
@@ -1168,7 +728,7 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
name={["parts_tax_rates", "CCD", "prt_mkupp"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
@@ -1181,39 +741,39 @@ export function JobsDetailRatesParts({
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
name={["parts_tax_rates", "CCD", "prt_tax_rt"]}
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow>
<Form.Item label={t("jobs.fields.tax_tow_rt")} name="tax_tow_rt">
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.tax_str_rt")} name="tax_str_rt">
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.tax_paint_mat_rt")}
name="tax_paint_mat_rt"
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.tax_shop_mat_rt")}
name="tax_shop_mat_rt"
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.tax_sub_rt")} name="tax_sub_rt">
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.tax_lbr_rt")} name="tax_lbr_rt">
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.tax_levies_rt")}
name="tax_levies_rt"
>
<InputNumber min={0} max={100} precision={4} disabled={jobRO} />
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
</Collapse.Panel>

View File

@@ -1,42 +0,0 @@
import { Button, Popconfirm } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
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))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsDetailRatesProfileOVerride);
export function JobsDetailRatesProfileOVerride({ bodyshop, form }) {
const { t } = useTranslation();
return (
<Popconfirm
onConfirm={() => {
form.setFieldsValue({
cieca_pft: {
...bodyshop.md_responsibility_centers.taxes.tax_ty1,
...bodyshop.md_responsibility_centers.taxes.tax_ty2,
...bodyshop.md_responsibility_centers.taxes.tax_ty3,
...bodyshop.md_responsibility_centers.taxes.tax_ty4,
...bodyshop.md_responsibility_centers.taxes.tax_ty5,
},
materials: bodyshop.md_responsibility_centers.cieca_pfm,
cieca_pfl: bodyshop.md_responsibility_centers.cieca_pfl,
parts_tax_rates: bodyshop.md_responsibility_centers.parts_tax_rates,
});
}}
title={t("jobs.actions.taxprofileoverride_confirm")}
>
<Button type="link">{t("jobs.actions.taxprofileoverride")}</Button>
</Popconfirm>
);
}

View File

@@ -1,155 +0,0 @@
import { Collapse, Divider, Form, Input, InputNumber, Space } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
});
export function JobsDetailRatesTaxes({
jobRO,
expanded,
bodyshop,
required = true,
form,
}) {
const { t } = useTranslation();
const formItems = [];
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
const section = [];
section.push(
TaxFormItems({
typeNum: tyCounter,
rootElements: true,
bodyshop,
jobRO,
})
);
for (let iterator = 1; iterator <= 5; iterator++) {
section.push(
TaxFormItems({
typeNum: tyCounter,
typeNumIterator: iterator,
rootElements: false,
jobRO,
})
);
}
formItems.push(Space({ children: section, wrap: true }));
formItems.push(<Divider />);
}
return (
<Collapse defaultActiveKey={expanded && "rates"}>
<Collapse.Panel
forceRender
header={t("jobs.labels.cieca_pft")}
key="cieca_pft"
>
{formItems}
</Collapse.Panel>
</Collapse>
);
}
export default connect(mapStateToProps, null)(JobsDetailRatesTaxes);
function TaxFormItems({
typeNum,
typeNumIterator,
rootElements,
bodyshopjobRO,
jobRO,
}) {
const { t } = useTranslation();
if (rootElements)
return (
<>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_tax_type", {
typeNum,
typeNumIterator,
})}
// rules={[
// {
// required: true,
// //message: t("general.validation.required"),
// },
// ]}
name={["cieca_pft", `tax_type${typeNum}`]}
>
<Input disabled={jobRO} />
</Form.Item>
</>
);
return (
<>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_tax_tier", {
typeNum,
typeNumIterator,
})}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["cieca_pft", `ty${typeNum}_tier${typeNumIterator}`]}
>
<InputNumber precision={0} min={0} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_tax_thres", {
typeNum,
typeNumIterator,
})}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["cieca_pft", `ty${typeNum}_thres${typeNumIterator}`]}
>
<InputNumber min={0} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_tax_rate", {
typeNum,
typeNumIterator,
})}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["cieca_pft", `ty${typeNum}_rate${typeNumIterator}`]}
>
<InputNumber min={0} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_tax_sur", {
typeNum,
typeNumIterator,
})}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["cieca_pft", `ty${typeNum}_sur${typeNumIterator}`]}
>
<InputNumber min={0} precision={2} disabled={jobRO} />
</Form.Item>
</>
);
}

View File

@@ -9,12 +9,10 @@ import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_JOBS } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({
@@ -22,11 +20,6 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
function updateJobCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
@@ -48,7 +41,6 @@ export function JobsExportAllButton({
loadingCallback,
completedCallback,
refetch,
insertAuditTrail,
}) {
const { t } = useTranslation();
const [updateJob] = useMutation(UPDATE_JOBS);
@@ -185,12 +177,6 @@ export function JobsExportAllButton({
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
jobUpdateResponse.data.update_jobs.returning.forEach((job) => {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobexported(),
});
});
updateJobCache(
jobUpdateResponse.data.update_jobs.returning.map(
(job) => job.id
@@ -204,17 +190,13 @@ export function JobsExportAllButton({
});
}
}
if (
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
successfulTransactions.length > 0
) {
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
const successfulTransactionsSet = [
updateJobCache([
...new Set(
successfulTransactions.map(
(st) =>
@@ -225,14 +207,7 @@ export function JobsExportAllButton({
]
)
),
];
if (successfulTransactionsSet.length > 0) {
insertAuditTrail({
jobid: successfulTransactionsSet[0],
operation: AuditTrailMapping.jobexported(),
});
}
updateJobCache(successfulTransactionsSet);
]);
}
}
})
@@ -250,7 +225,4 @@ export function JobsExportAllButton({
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsExportAllButton);
export default connect(mapStateToProps, null)(JobsExportAllButton);

View File

@@ -1,11 +1,9 @@
import { SyncOutlined } from "@ant-design/icons";
import { Button, Checkbox, Divider, Input, Space, Table } from "antd";
import moment from "moment";
import React, { useState } from "react";
import { Checkbox, Divider, Input, Table, Button } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import PhoneFormatter from "../../utils/PhoneFormatter";
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
export default function JobsFindModalComponent({
@@ -18,13 +16,11 @@ export default function JobsFindModalComponent({
jobsListRefetch,
partsQueueToggle,
setPartsQueueToggle,
updateSchComp,
setSchComp,
}) {
const { t } = useTranslation();
const [modalSearch, setModalSearch] = modalSearchState;
const [importOptions, setImportOptions] = importOptionsState;
const [checkUTT, setCheckUTT] = useState(false);
const columns = [
{
title: t("jobs.fields.ro_number"),
@@ -146,35 +142,6 @@ export default function JobsFindModalComponent({
if (record) {
if (record.id) {
setSelectedJob(record.id);
if (record.actual_in && record.scheduled_completion) {
setSchComp({
...updateSchComp,
actual_in: record.actual_in,
scheduled_completion: record.scheduled_completion,
});
} else {
if (record.actual_in && !record.scheduled_completion) {
setSchComp({
...updateSchComp,
actual_in: record.actual_in,
scheduled_completion: moment(),
});
}
if (!record.actual_in && record.scheduled_completion) {
setSchComp({
...updateSchComp,
actual_in: moment(),
scheduled_completion: moment(record.scheduled_completion),
});
}
if (!record.actual_in && !record.scheduled_completion) {
setSchComp({
...updateSchComp,
actual_in: moment(),
scheduled_completion: moment(),
});
}
}
return;
}
}
@@ -210,35 +177,6 @@ export default function JobsFindModalComponent({
rowSelection={{
onSelect: (props) => {
setSelectedJob(props.id);
if (props.actual_in && props.scheduled_completion) {
setSchComp({
...updateSchComp,
actual_in: props.actual_in,
scheduled_completion: props.scheduled_completion,
});
} else {
if (props.actual_in && !props.scheduled_completion) {
setSchComp({
...updateSchComp,
actual_in: props.actual_in,
scheduled_completion: moment(),
});
}
if (!props.actual_in && props.scheduled_completion) {
setSchComp({
...updateSchComp,
actual_in: moment(),
scheduled_completion: moment(props.scheduled_completion),
});
}
if (!props.actual_in && !props.scheduled_completion) {
setSchComp({
...updateSchComp,
actual_in: moment(),
scheduled_completion: moment(),
});
}
}
},
type: "radio",
selectedRowKeys: [selectedJob],
@@ -252,58 +190,23 @@ export default function JobsFindModalComponent({
}}
/>
<Divider />
<Space>
<Checkbox
defaultChecked={importOptions.overrideHeader}
onChange={(e) =>
setImportOptions({
...importOptions,
overrideHeaders: e.target.checked,
})
}
>
{t("jobs.labels.override_header")}
</Checkbox>
<Checkbox
<Checkbox
defaultChecked={importOptions.overrideHeader}
onChange={(e) =>
setImportOptions({
...importOptions,
overrideHeaders: e.target.checked,
})
}
>
{t("jobs.labels.override_header")}
</Checkbox>
<Checkbox
checked={partsQueueToggle}
onChange={(e) => setPartsQueueToggle(e.target.checked)}
>
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
</Checkbox>
<Checkbox
checked={updateSchComp.checked}
onChange={(e) =>
setSchComp({ ...updateSchComp, checked: e.target.checked })
}
>
{t("jobs.labels.update_scheduled_completion")}
</Checkbox>
{updateSchComp.checked === true ? (
<>
{checkUTT === false ? (
<FormDateTimePickerComponent
value={updateSchComp.scheduled_completion}
onChange={(e) => {
setSchComp({ ...updateSchComp, scheduled_completion: e });
}}
/>
) : null}
<Checkbox
checked={checkUTT}
onChange={(e) => {
setCheckUTT(e.target.checked);
setSchComp({
...updateSchComp,
scheduled_completion: null,
automatic: true,
});
}}
>
{t("jobs.labels.calc_scheuled_completion")}
</Checkbox>
</>
) : null}
</Space>
>
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
</Checkbox>
</div>
);
}

View File

@@ -26,8 +26,6 @@ export default connect(
modalSearchState,
partsQueueToggle,
setPartsQueueToggle,
updateSchComp,
setSchComp,
...modalProps
}) {
const { t } = useTranslation();
@@ -97,8 +95,6 @@ export default connect(
modalSearchState={modalSearchState}
partsQueueToggle={partsQueueToggle}
setPartsQueueToggle={setPartsQueueToggle}
updateSchComp={updateSchComp}
setSchComp={setSchComp}
/>
) : null}
</Modal>

View File

@@ -10,10 +10,9 @@ import { Link, useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { pageLimit } from "../../utils/config";
import useLocalStorage from "../../utils/useLocalStorage";
import StartChatButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -26,7 +25,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const [filter, setFilter] = useLocalStorage("filter_jobs_all", null);
const { page, sortcolumn, sortorder } = search;
const history = useHistory();
@@ -95,7 +93,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
render: (text, record) => {
return record.status || t("general.labels.na");
},
filteredValue: filter?.status || null,
filters: bodyshop.md_ro_statuses.statuses.map((s) => {
return { text: s, value: [s] };
}),
@@ -192,7 +189,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
} else {
delete search.statusFilters;
}
setFilter(filters);
history.push({ search: queryString.stringify(search) });
};

View File

@@ -1,402 +1,426 @@
import {
BranchesOutlined,
ExclamationCircleFilled,
PauseCircleOutlined,
SyncOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
import {BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, SyncOutlined,} from "@ant-design/icons";
import {useQuery} from "@apollo/client";
import {Button, Card, Grid, Input, Space, Table, Tooltip, Typography} from "antd";
import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {Link, useHistory, useLocation} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {QUERY_ALL_ACTIVE_JOBS_PAGINATED} from "../../graphql/jobs.queries";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {onlyUnique} from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort, statusSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {flattenDeep} from "lodash";
import { pageLimit } from '../../utils/config';
import axios from "axios";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
bodyshop: selectBodyshop,
});
export function JobsList({ bodyshop }) {
const searchParams = queryString.parse(useLocation().search);
const { selected } = searchParams;
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const mapDispatchToProps = () => ({
});
const [state, setState] = useState({ sortedInfo: {} });
const [filter, setFilter] = useLocalStorage("filter_jobs_list", null);
export function JobsList({bodyshop,}) {
const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const {page, selected, sortorder, sortcolumn,statusFilters} = search;
const { t } = useTranslation();
const history = useHistory();
const [searchText, setSearchText] = useState("");
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
if (error) return <AlertComponent message={error.message} type="error" />;
const {loading, error, data, refetch} = useQuery(QUERY_ALL_ACTIVE_JOBS_PAGINATED, {
variables: {
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
statusFilters: statusFilters ? JSON.parse(statusFilters) : bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
order: [
{
[sortcolumn || "ro_number"]:
sortorder && sortorder !== "false"
? (sortorder === "descend"
? "desc"
: "asc")
: "desc",
},
],
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const jobs = data
? searchText === ""
? data.jobs
: data.jobs.filter(
(j) =>
(j.ro_number || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.comments || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.plate_no || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.est_ct_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.est_ct_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
)
: [];
const total = data?.jobs_aggregate?.aggregate?.count || 0;
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, sortedInfo: sorter });
setFilter(filters);
};
const [state, setState] = useState({
filteredInfo: {text: ""},
});
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
history.push({
search: queryString.stringify({
...searchParams,
selected: record.id,
}),
});
}
const {t} = useTranslation();
const history = useHistory();
const jobs = data?.jobs || [];
const handleTableChange = (pagination, filters, sorter) => {
search.page = pagination.current;
search.sortcolumn = sorter.column && sorter.column.key;
search.sortorder = sorter.order;
if (filters.status) {
search.statusFilters = JSON.stringify(flattenDeep(filters.status));
} else {
delete search.statusFilters;
}
history.push({search: queryString.stringify(search)});
};
useEffect(() => {
if (search.search && search.search.trim() !== "") {
searchJobs().catch(e => {
console.error('Something went wrong searching for jobs in the job-list component', e);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (error) return <AlertComponent message={error.message} type="error"/>;
async function searchJobs(value) {
try {
setSearchLoading(true);
const searchData = await axios.post("/search", {
search: value || search.search,
index: "jobs",
});
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setSearchLoading(false);
}
}
};
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) =>
parseInt((a.ro_number || "0").replace(/\D/g, "")) -
parseInt((b.ro_number || "0").replace(/\D/g, "")),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.id}
onClick={(e) => e.stopPropagation()}
>
<Space>
{record.ro_number || t("general.labels.na")}
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
</Space>
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => {
return record.ownerid ? (
<Link
to={"/manage/owners/" + record.ownerid}
onClick={(e) => e.stopPropagation()}
>
<OwnerNameDisplay ownerObject={record} />
</Link>
) : (
<span>
<OwnerNameDisplay ownerObject={record} />
const handleOnRowClick = (record) => {
if (record) {
if (record.id) {
history.push({
search: queryString.stringify({
...search,
selected: record.id,
}),
});
}
}
};
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: true,
sortOrder:
sortcolumn === "ro_number" && sortorder,
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.id}
onClick={(e) => e.stopPropagation()}
>
<Space>
{record.ro_number || t("general.labels.na")}
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert"/>
) : null}
{record.suspended && (
<PauseCircleOutlined style={{color: "orangered"}}/>
)}
{record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{color: "orangered"}}/>
</Tooltip>
)}
</Space>
</Link>
),
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
ellipsis: true,
responsive: ["md"],
sorter: false,
sortOrder: sortcolumn === "owner" && sortorder,
render: (text, record) => {
return record.ownerid ? (
<Link
to={"/manage/owners/" + record.ownerid}
onClick={(e) => e.stopPropagation()}
>
<OwnerNameDisplay ownerObject={record}/>
</Link>
) : (
<span>
<OwnerNameDisplay ownerObject={record}/>
</span>
);
},
},
{
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
),
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
ellipsis: true,
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filteredValue: filter?.status || null,
filters:
(jobs &&
jobs
.map((j) => j.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})
.sort((a, b) =>
statusSort(
a.text,
b.text,
bodyshop.md_ro_statuses.active_statuses
)
)) ||
[],
onFilter: (value, record) => value.includes(record.status),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link
to={"/manage/vehicles/" + record.vehicleid}
onClick={(e) => e.stopPropagation()}
>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</Link>
) : (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
);
},
},
{
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder:
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
render: (text, record) =>
`${record.clm_no || ""}${
record.po_number ? ` (PO: ${record.po_number})` : ""
}`,
},
{
title: t("jobs.fields.ins_co_nm"),
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
filteredValue: filter?.ins_co_nm || null,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Ins. Co.*",
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"],
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
responsive: ["md"],
ellipsis: true,
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.labels.estimator"),
dataIndex: "jobs.labels.estimator",
key: "estimator",
ellipsis: true,
responsive: ["xl"],
filterSearch: true,
filteredValue: filter?.estimator || null,
filters:
(jobs &&
jobs
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Estimator*",
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
[],
onFilter: (value, record) =>
value.includes(
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
responsive: ["md"],
},
// {
// title: t("jobs.fields.owner_owing"),
// dataIndex: "owner_owing",
// key: "owner_owing",
// responsive: ["md"],
// render: (text, record) => (
// <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
// ),
// },
];
const scrollMapper = {
xs: true,
sm: true,
md: true,
lg: "100%",
xl: "100%",
xxl: "100%",
};
return (
<Card
title={t("titles.bc.jobs-active")}
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
/>
</Space>
}
>
<Table
loading={loading}
pagination={{ defaultPageSize: 50 }}
columns={columns}
rowKey="id"
dataSource={jobs}
scroll={{
x: selectedBreakpoint ? scrollMapper[selectedBreakpoint[0]] : "100%",
}}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selected],
type: "radio",
}}
onChange={handleTableChange}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
);
},
};
}}
/>
</Card>
);
},
{
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id}/>
),
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id}/>
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
ellipsis: true,
sorter: true,
sortOrder:
sortcolumn === "status" && sortorder,
filters:bodyshop.md_ro_statuses.statuses.map((s) => {
return { text: s, value: [s] };
}),
onFilter: (value, record) => value.includes(record.status),
render: (text, record) => {
return record.status || t("general.labels.na");
},
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link
to={"/manage/vehicles/" + record.vehicleid}
onClick={(e) => e.stopPropagation()}
>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</Link>
) : (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
);
},
},
{
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
ellipsis: true,
responsive: ["md"],
sorter: true,
sortOrder:
sortcolumn === "plate_no" && sortorder,
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
responsive: ["md"],
sorter: true,
sortOrder:
sortcolumn === "clm_no" && sortorder,
render: (text, record) =>
`${record.clm_no || ""}${
record.po_number ? ` (PO: ${record.po_number})` : ""
}`,
},
{
title: t("jobs.fields.ins_co_nm"),
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
// TODO: Restore Filters?
// filters:
// (jobs &&
// jobs
// .map((j) => j.ins_co_nm)
// .filter(onlyUnique)
// .map((s) => {
// return {
// text: s,
// value: [s],
// };
// })) ||
// [],
// onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"],
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
responsive: ["md"],
ellipsis: true,
sorter: true,
sortOrder:
sortcolumn === "clm_total" && sortorder,
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.labels.estimator"),
dataIndex: "jobs.labels.estimator",
key: "jobs.labels.estimator",
ellipsis: true,
responsive: ["xl"],
// TODO Restore Filters?
// filterSearch: true,
// filters:
// (jobs &&
// jobs
// .map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
// .filter(onlyUnique)
// .map((s) => {
// return {
// text: s || "N/A",
// value: [s],
// };
// })) ||
// [],
// onFilter: (value, record) =>
// value.includes(
// `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
// ),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
responsive: ["md"],
},
// {
// title: t("jobs.fields.owner_owing"),
// dataIndex: "owner_owing",
// key: "owner_owing",
// responsive: ["md"],
// render: (text, record) => (
// <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
// ),
// },
];
const scrollMapper = {
xs: true,
sm: true,
md: true,
lg: "100%",
xl: "100%",
xxl: "100%",
};
return (
<Card
title={t("titles.bc.jobs-active")}
extra={
<Space wrap>
{search.search && (
<>
<Typography.Title level={4}>
{t("general.labels.searchresults", { search: search.search })}
</Typography.Title>
<Button
onClick={() => {
delete search.search;
delete search.page;
history.push({ search: queryString.stringify(search) });
}}
>
{t("general.actions.clear")}
</Button>
</>
)}
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
placeholder={search.search || t("general.labels.search")}
onSearch={(value) => {
search.search = value;
history.push({ search: queryString.stringify(search) });
searchJobs(value);
}}
loading={loading || searchLoading}
enterButton
/>
</Space>
}
>
<Table
loading={loading || searchLoading }
pagination={
search?.search
? {
pageSize: pageLimit,
showSizeChanger: false,
}
: {
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
}
columns={columns}
rowKey="id"
dataSource={search?.search ? openSearchResults : jobs}
scroll={{
x: selectedBreakpoint ? scrollMapper[selectedBreakpoint[0]] : "100%",
}}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selected],
type: "radio",
}}
onChange={handleTableChange}
onRow={(record) => {
return {
onClick: () => {
handleOnRowClick(record);
},
};
}}
/>
</Card>
);
}
export default connect(mapStateToProps, null)(JobsList);
export default connect(mapStateToProps, mapDispatchToProps)(JobsList);

View File

@@ -16,12 +16,11 @@ import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import { pageLimit } from "../../utils/config";
import { alphaSort, statusSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
import { alphaSort } from "../../utils/sorters";
import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -54,8 +53,10 @@ export function JobsReadyList({ bodyshop }) {
nextFetchPolicy: "network-only",
});
const [state, setState] = useState({ sortedInfo: {} });
const [filter, setFilter] = useLocalStorage("filter_jobs_ready", null);
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const { t } = useTranslation();
const history = useHistory();
@@ -104,8 +105,7 @@ export function JobsReadyList({ bodyshop }) {
: [];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, sortedInfo: sorter });
setFilter(filters);
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const handleOnRowClick = (record) => {
@@ -129,6 +129,7 @@ export function JobsReadyList({ bodyshop }) {
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => (
<Link
to={"/manage/jobs/" + record.id}
@@ -156,6 +157,7 @@ export function JobsReadyList({ bodyshop }) {
dataIndex: "owner",
key: "owner",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder:
@@ -195,15 +197,16 @@ export function JobsReadyList({ bodyshop }) {
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
ellipsis: true,
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filteredValue: filter?.status || null,
filters:
(jobs &&
jobs
@@ -214,17 +217,11 @@ export function JobsReadyList({ bodyshop }) {
text: s || "No Status*",
value: [s],
};
})
.sort((a, b) =>
statusSort(
a.text,
b.text,
bodyshop.md_ro_statuses.active_statuses
)
)) ||
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
@@ -277,7 +274,6 @@ export function JobsReadyList({ bodyshop }) {
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
filteredValue: filter?.ins_co_nm || null,
filters:
(jobs &&
jobs
@@ -285,11 +281,10 @@ export function JobsReadyList({ bodyshop }) {
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Ins Co.*",
text: s,
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"],
@@ -300,6 +295,7 @@ export function JobsReadyList({ bodyshop }) {
key: "clm_total",
responsive: ["md"],
ellipsis: true,
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
@@ -310,10 +306,9 @@ export function JobsReadyList({ bodyshop }) {
{
title: t("jobs.labels.estimator"),
dataIndex: "jobs.labels.estimator",
key: "estimator",
key: "jobs.labels.estimator",
ellipsis: true,
responsive: ["xl"],
filteredValue: filter?.estimator || null,
filterSearch: true,
filters:
(jobs &&
@@ -322,11 +317,10 @@ export function JobsReadyList({ bodyshop }) {
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Estimator*",
text: s || "N/A",
value: [s],
};
})
.sort((a, b) => alphaSort(a.text, b.text))) ||
})) ||
[],
onFilter: (value, record) =>
value.includes(

Some files were not shown because too many files have changed in this diff Show More