Compare commits

..

1 Commits

Author SHA1 Message Date
Patrick Fic
92103df2a9 IO-2348 add http auth for media. 2023-07-11 15:46:58 -07:00
184 changed files with 14819 additions and 36191 deletions

View File

@@ -8,13 +8,13 @@ orbs:
jobs: jobs:
api-deploy: api-deploy:
docker: docker:
- image: cimg/node:18.18.2 - image: "cimg/base:stable"
steps: steps:
- checkout - checkout
- eb/setup - eb/setup
- run: - run:
command: | command: |
eb init imex-online-production-api -r ca-central-1 -p "Node.js 18 running on 64bit Amazon Linux 2" eb init imex-online-production-api -r ca-central-1 -p "Node.js 16 running on 64bit Amazon Linux 2"
eb status --verbose eb status --verbose
eb deploy eb deploy
eb status eb status

4
.gitignore vendored
View File

@@ -117,6 +117,4 @@ logs/oAuthClient-log.log
.node-persist/** .node-persist/**
/*.env.* /*.env.*
.idea/*
.idea

1
.npmrc
View File

@@ -1 +0,0 @@
legacy-peer-deps=true

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1"> <babeledit_project be_version="2.7.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -364,27 +364,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>unblock</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>viewjob</name> <name>viewjob</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -1030,6 +1009,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>smspaymentreminder</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>suggesteddates</name> <name>suggesteddates</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -1474,27 +1474,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>admin_jobuninvoice</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>admin_jobunvoid</name> <name>admin_jobunvoid</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -3592,27 +3571,6 @@
<folder_node> <folder_node>
<name>validation</name> <name>validation</name>
<children> <children>
<concept_node>
<name>closingperiod</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>inventoryquantity</name> <name>inventoryquantity</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4247,27 +4205,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>closingperiod</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>country</name> <name>country</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -4381,27 +4318,6 @@
<folder_node> <folder_node>
<name>dms</name> <name>dms</name>
<children> <children>
<concept_node>
<name>apcontrol</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>appostingaccount</name> <name>appostingaccount</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -16228,27 +16144,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>copied</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>copylink</name> <name>copylink</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -16585,27 +16480,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>sendbysms</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>senderrortosupport</name> <name>senderrortosupport</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -17776,27 +17650,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>reports</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>required</name> <name>required</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -19339,27 +19192,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>openingip</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>title</name> <name>title</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -19514,27 +19346,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>paymentnum</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>paymenttype</name> <name>paymenttype</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -23857,27 +23668,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>date_void</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>ded_amt</name> <name>ded_amt</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -24138,27 +23928,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>dms_model_override</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>dms_unsold</name> <name>dms_unsold</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -28679,27 +28448,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>closingperiod</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>contracts</name> <name>contracts</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -33110,6 +32858,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>paymentremindersms</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>phonebook</name> <name>phonebook</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -37314,32 +37083,6 @@
<folder_node> <folder_node>
<name>payments</name> <name>payments</name>
<children> <children>
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>generatepaymentlink</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node> <folder_node>
<name>errors</name> <name>errors</name>
<children> <children>
@@ -37385,27 +37128,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>inserting</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -37878,27 +37600,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>smspaymentreminder</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>title</name> <name>title</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -40963,27 +40664,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>parts_return_slip</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>sublet_order</name> <name>sublet_order</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -43344,27 +43024,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>hours_sold_detail_closed_estimator</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>hours_sold_detail_closed_ins_co</name> <name>hours_sold_detail_closed_ins_co</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -43449,27 +43108,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>hours_sold_detail_open_estimator</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>hours_sold_detail_open_ins_co</name> <name>hours_sold_detail_open_ins_co</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -43554,27 +43192,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>hours_sold_summary_closed_estimator</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>hours_sold_summary_closed_ins_co</name> <name>hours_sold_summary_closed_ins_co</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -43659,27 +43276,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>hours_sold_summary_open_estimator</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>hours_sold_summary_open_ins_co</name> <name>hours_sold_summary_open_ins_co</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -44016,27 +43612,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>open_orders_referral</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>open_orders_specific_csr</name> <name>open_orders_specific_csr</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -44940,27 +44515,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>work_in_progress_jobs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>work_in_progress_labour</name> <name>work_in_progress_labour</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -46416,27 +45970,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>created_by</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>date</name> <name>date</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

24522
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"@apollo/client": "^3.7.9", "@apollo/client": "^3.7.9",
"@asseinfo/react-kanban": "^2.2.0", "@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^7.0.0", "@craco/craco": "^7.0.0",
"@fingerprintjs/fingerprintjs": "^3.4.2", "@fingerprintjs/fingerprintjs": "^3.3.3",
"@jsreport/browser-client": "^3.1.0", "@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^7.40.0", "@sentry/react": "^7.40.0",
"@sentry/tracing": "^7.40.0", "@sentry/tracing": "^7.40.0",
@@ -89,8 +89,8 @@
"analyze": "source-map-explorer 'build/static/js/*.js'", "analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "craco start", "start": "craco start",
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build", "build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"build:test": "env-cmd -f .env.test npm run build", "build:test": "env-cmd -f .env.test yarn run build",
"build-deploy:test": "npm run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'", "build-deploy:test": "yarn run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build", "buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"test": "cypress open", "test": "cypress open",
"eject": "react-scripts eject", "eject": "react-scripts eject",

View File

@@ -0,0 +1,49 @@
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { setEmailOptions } from "../../redux/email/email.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
function Test({ bodyshop, setEmailOptions }) {
return (
<div>
<button
onClick={() => {
setEmailOptions({
messageOptions: {
to: ["patrickwf@gmail.com"],
replyTo: bodyshop.email,
},
template: {
name: TemplateList().parts_order.key,
variables: {
id: "a7c2d4e1-f519-42a9-a071-c48cf0f22979",
},
},
});
}}
>
send email
</button>
<button
onClick={() => {
logImEXEvent("IMEXEVENT", { somethignArThare: 5 });
}}
>
Log an ImEX Event.
</button>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Test);

View File

@@ -1,63 +0,0 @@
{
"status": 24201299,
"custid": 19607899,
"paymentid": 24201299,
"response": "A",
"authcode": "498680",
"declinereason": "Approved",
"fee": 0,
"invoice": "",
"account": "john",
"amount": 1000,
"amountincludesfee": false,
"total": 1000,
"paymenttype": "C",
"methodhint": "VI ***1111",
"cardbrand": "Visa",
"cardnumdisplay": "***1111",
"receiptelements": {
"authcode": "498680",
"cust_srv_ph_num": "1-555-555-5555",
"rcpt_pg_ftr_txt": "Thank You\nPlease Come Again",
"rcpt_currency": "USD",
"responsecode": "A",
"rcpt_pay_mthd": "Visa",
"transid": "C00 915799",
"merch_disp_nm": "CP Devel Test",
"rcpt_input_mthd": "Keyed",
"rcpt_pg_hdr_txt": "Welcome!",
"rcpt_tran_time": "Thursday February 23 2023, 11:25:36 pm +08",
"rcpt_trans_type": "Normal Transaction (Sale)",
"message": "Approved",
"rcpt_dba_addr": "1234 Storefront Ave\nSome City, UT 84111",
"avsdata": "N",
"receiptrequirements": "S",
"rcpt_cardnum": "************1111",
"cv2result": "M",
"rfnd_policy_txt": "<b>No Refunds</b>\nStore Credit Only",
"labels": {
"tranref": "REF#",
"tid": "TID",
"validationcode": "ValCode",
"emvapplicationid": "AID",
"emvatc": "ATC",
"rcpt_pay_mthd": "Pay Method",
"transid": "TransID",
"rcpt_input_mthd": "IMode",
"emvtsi": "TSI",
"emvac": "AC",
"rcpt_trans_type": "TranType",
"emvapplicationname": "PApp",
"visarewards": "RewardsProg"
}
},
"receipttoken": "H4sIAAAAAAAAACXMTQuCMBgA4P/ynh3tw_3dBI/ipQ8NOtRN53QiblpBRfTfCzo/8LwhxGAdZCCwFYoJJFQjI2kvHdGu74lVkgmrWyWNhASW5jW7cB87yHjKKePGJODnxrrnMl7dDTKmEJlSOqV/_N30XPpyj2Eddq57_KKZ8FLzmh_G1VQnVfhjiXGK1XYTc/h8AVOkf4qUAAAA",
"call": "card_payment",
"nonce": "488b5568-b5c1-4f38-8b2f-3b050f3abb11P",
"hmac": "JyPAJ9Yx0SlYBTtqns1OxAFRt+xF3l2UiLPO5zTDRBE=",
"paymentreferenceid": "C19607899P24201299",
"cardnum": "...1111",
"email": "",
"nameOnCard": "John Allen",
"cardType": "visa"
}

View File

@@ -0,0 +1,9 @@
import React from "react";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
export default function Test() {
return (
<div>
<QboAuthorizeComponent />
</div>
);
}

View File

@@ -1,31 +0,0 @@
import { Button } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setRefundPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "refund_payment" })),
});
function Test({ setRefundPaymentContext, refundPaymentModal }) {
console.log("refundPaymentModal", refundPaymentModal);
return (
<div>
<Button
onClick={() =>
setRefundPaymentContext({
context: {},
})
}
>
Open Modal
</Button>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Test);

View File

@@ -37,8 +37,8 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")), toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
insertAuditTrail: ({ jobid, billid, operation }) => insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, billid, operation })), dispatch(insertAuditTrail({ jobid, operation })),
}); });
const Templates = TemplateList("job_special"); const Templates = TemplateList("job_special");
@@ -143,7 +143,7 @@ function BillEnterModalContainer({
}, },
], ],
}, },
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"], refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
}); });
const adjKeys = Object.keys(adjustmentsToInsert); const adjKeys = Object.keys(adjustmentsToInsert);
@@ -316,9 +316,7 @@ function BillEnterModalContainer({
insertAuditTrail({ insertAuditTrail({
jobid: values.jobid, jobid: values.jobid,
billid: billId, billid: billId,
operation: AuditTrailMapping.billposted( operation: AuditTrailMapping.billposted(remainingValues.invoice_number),
r1.data.insert_bills.returning[0].invoice_number
),
}); });
if (enterAgain) { if (enterAgain) {

View File

@@ -1,6 +1,6 @@
import Icon, { UploadOutlined } from "@ant-design/icons"; import Icon, { UploadOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client"; import { useApolloClient } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react"; import { MdOpenInNew } from "react-icons/md";
import { import {
Alert, Alert,
Divider, Divider,
@@ -12,17 +12,14 @@ import {
Switch, Switch,
Upload, Upload,
} from "antd"; } from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { MdOpenInNew } from "react-icons/md";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries"; import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
@@ -31,6 +28,8 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component"; import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility"; import { CalculateBillTotal } from "./bill-form.totals.utility";
import { useTreatments } from "@splitsoftware/splitio-react";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -50,7 +49,6 @@ export function BillFormComponent({
job, job,
loadOutstandingReturns, loadOutstandingReturns,
loadInventory, loadInventory,
preferredMake,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
@@ -60,11 +58,6 @@ export function BillFormComponent({
{}, {},
bodyshop.imexshopid bodyshop.imexshopid
); );
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop.imexshopid
);
const handleVendorSelect = (props, opt) => { const handleVendorSelect = (props, opt) => {
setDiscount(opt.discount); setDiscount(opt.discount);
@@ -186,7 +179,6 @@ export function BillFormComponent({
<VendorSearchSelect <VendorSearchSelect
disabled={disabled} disabled={disabled}
options={vendorAutoCompleteOptions} options={vendorAutoCompleteOptions}
preferredMake={preferredMake}
onSelect={handleVendorSelect} onSelect={handleVendorSelect}
/> />
</Form.Item> </Form.Item>
@@ -267,37 +259,6 @@ export function BillFormComponent({
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
({ getFieldValue }) => ({
validator(rule, value) {
if (
ClosingPeriod.treatment === "on" &&
bodyshop.accountingconfig.ClosingPeriod
) {
if (
moment(value)
.startOf("day")
.isSameOrAfter(
moment(
bodyshop.accountingconfig.ClosingPeriod[0]
).startOf("day")
) &&
moment(value)
.startOf("day")
.isSameOrBefore(
moment(
bodyshop.accountingconfig.ClosingPeriod[1]
).endOf("day")
)
) {
return Promise.resolve();
} else {
return Promise.reject(t("bills.validation.closingperiod"));
}
} else {
return Promise.resolve();
}
},
}),
]} ]}
> >
<FormDatePicker disabled={disabled} /> <FormDatePicker disabled={disabled} />

View File

@@ -1,16 +1,16 @@
import { useLazyQuery, useQuery } from "@apollo/client"; import { useLazyQuery, useQuery } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries"; import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries";
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries"; import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
import BillFormComponent from "./bill-form.component"; import BillFormComponent from "./bill-form.component";
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -59,7 +59,6 @@ export function BillFormContainer({
disableInvNumber={disableInvNumber} disableInvNumber={disableInvNumber}
loadOutstandingReturns={loadOutstandingReturns} loadOutstandingReturns={loadOutstandingReturns}
loadInventory={loadInventory} loadInventory={loadInventory}
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
/> />
{!billEdit && ( {!billEdit && (
<BillCmdReturnsTableComponent <BillCmdReturnsTableComponent

View File

@@ -1,374 +0,0 @@
import { DeleteFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client";
import {
Button,
Card,
Col,
Form,
Input,
Row,
Space,
Spin,
Statistic,
notification,
} from "antd";
import axios from "axios";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
INSERT_PAYMENT_RESPONSE,
QUERY_RO_AND_OWNER_BY_JOB_PKS,
} from "../../graphql/payment_response.queries";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
const mapStateToProps = createStructuredSelector({
cardPaymentModal: selectCardPayment,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
});
const CardPaymentModalComponent = ({
bodyshop,
cardPaymentModal,
toggleModalVisible,
insertAuditTrail,
}) => {
const { context } = cardPaymentModal;
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation();
const [, { data, refetch, queryLoading }] = useLazyQuery(
QUERY_RO_AND_OWNER_BY_JOB_PKS,
{
variables: { jobids: [context.jobid] },
skip: true,
}
);
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
//Initialize the intellipay window.
const SetIntellipayCallbackFunctions = () => {
console.log("*** Set IntelliPay callback functions.");
window.intellipay.runOnClose(() => {
//window.intellipay.initialize();
});
window.intellipay.runOnApproval(async function (response) {
console.warn("*** Running On Approval Script ***");
form.setFieldValue("paymentResponse", response);
form.submit();
});
window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment
const { payments } = form.getFieldsValue();
await insertPaymentResponse({
variables: {
paymentResponse: payments.map((payment) => ({
amount: payment.amount,
bodyshopid: bodyshop.id,
jobid: payment.jobid,
declinereason: response.declinereason,
ext_paymentid: response.paymentid.toString(),
successful: false,
response,
})),
},
});
payments.forEach((payment) =>
insertAuditTrail({
jobid: payment.jobid,
operation: AuditTrailMapping.failedpayment(),
})
);
});
};
const handleFinish = async (values) => {
try {
await insertPayment({
variables: {
paymentInput: values.payments.map((payment) => ({
amount: payment.amount,
transactionid: (values.paymentResponse.paymentid || "").toString(),
payer: t("payments.labels.customer"),
type: values.paymentResponse.cardbrand,
jobid: payment.jobid,
date: moment(Date.now()),
payment_responses: {
data: [
{
amount: payment.amount,
bodyshopid: bodyshop.id,
jobid: payment.jobid,
declinereason: values.paymentResponse.declinereason,
ext_paymentid: values.paymentResponse.paymentid.toString(),
successful: true,
response: values.paymentResponse,
},
],
},
})),
},
refetchQueries: ["GET_JOB_BY_PK"],
});
toggleModalVisible();
} catch (error) {
console.error(error);
notification.open({
type: "error",
message: t("payments.errors.inserting", { error: error.message }),
});
} finally {
setLoading(false);
}
};
const handleIntelliPayCharge = async () => {
setLoading(true);
//Validate
try {
await form.validateFields();
} catch (error) {
setLoading(false);
return;
}
try {
const response = await axios.post("/intellipay/lightbox_credentials", {
bodyshop,
refresh: !!window.intellipay,
});
if (window.intellipay) {
// eslint-disable-next-line no-eval
eval(response.data);
SetIntellipayCallbackFunctions();
window.intellipay.autoOpen();
} else {
var rg = document.createRange();
let node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node);
SetIntellipayCallbackFunctions();
window.intellipay.isAutoOpen = true;
window.intellipay.initialize();
}
} catch (error) {
notification.open({
type: "error",
message: t("job_payments.notifications.error.openingip"),
});
setLoading(false);
}
};
return (
<Card title="Card Payment">
<Spin spinning={loading}>
<Form
onFinish={handleFinish}
form={form}
layout="vertical"
initialValues={{
payments: context.jobid ? [{ jobid: context.jobid }] : [],
}}
>
<Form.List name={["payments"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Row gutter={[16, 16]}>
<Col span={16}>
<Form.Item
key={`${index}jobid`}
label={t("jobs.fields.ro_number")}
name={[field.name, "jobid"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<JobSearchSelectComponent
notExported={false}
clm_no
/>
</Form.Item>
</Col>
<Col span={6}>
<Form.Item
key={`${index}amount`}
label={t("payments.fields.amount")}
name={[field.name, "amount"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<CurrencyFormItemComponent />
</Form.Item>
</Col>
<Col span={2}>
<DeleteFilled
style={{ margin: "1rem" }}
onClick={() => {
remove(field.name);
}}
/>
</Col>
</Row>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item
shouldUpdate={(prevValues, curValues) =>
prevValues.payments?.map((p) => p?.jobid).join() !==
curValues.payments?.map((p) => p?.jobid).join()
}
>
{() => {
console.log("Updating the owner info section.");
//If all of the job ids have been fileld in, then query and update the IP field.
const { payments } = form.getFieldsValue();
if (
payments?.length > 0 &&
payments?.filter((p) => p?.jobid).length === payments?.length
) {
console.log("**Calling refetch.");
refetch({ jobids: payments.map((p) => p.jobid) });
}
console.log(
"Acc info",
data,
payments && data && data.jobs.length > 0
? data.jobs.map((j) => j.ro_number).join(", ")
: null
);
return (
<>
<Input
className="ipayfield"
data-ipayname="account"
//type="hidden"
value={
payments && data && data.jobs.length > 0
? data.jobs.map((j) => j.ro_number).join(", ")
: null
}
hidden
/>
<Input
className="ipayfield"
data-ipayname="email"
// type="hidden"
value={
payments && data && data.jobs.length > 0
? data.jobs.filter((j) => j.ownr_ea)[0]?.ownr_ea
: null
}
hidden
/>
</>
);
}}
</Form.Item>
<Form.Item
shouldUpdate={(prevValues, curValues) =>
prevValues.payments?.map((p) => p?.amount).join() !==
curValues.payments?.map((p) => p?.amount).join()
}
>
{() => {
const { payments } = form.getFieldsValue();
const totalAmountToCharge = payments?.reduce((acc, val) => {
return acc + (val?.amount || 0);
}, 0);
return (
<Space style={{ float: "right" }}>
<Statistic
title="Amount To Charge"
value={totalAmountToCharge}
precision={2}
/>
<Input
className="ipayfield"
data-ipayname="amount"
//type="hidden"
value={totalAmountToCharge?.toFixed(2)}
hidden
/>
<Button
type="primary"
// data-ipayname="submit"
className="ipayfield"
loading={queryLoading || loading}
disabled={!(totalAmountToCharge > 0)}
onClick={handleIntelliPayCharge}
>
{t("job_payments.buttons.proceedtopayment")}
</Button>
</Space>
);
}}
</Form.Item>
{/* Lightbox payment response when it is completed */}
<Form.Item name="paymentResponse" hidden>
<Input type="hidden" />
</Form.Item>
</Form>
</Spin>
</Card>
);
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(CardPaymentModalComponent);

View File

@@ -1,57 +0,0 @@
import { Button, Modal } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CardPaymentModalComponent from "./card-payment-modal.component.";
const mapStateToProps = createStructuredSelector({
cardPaymentModal: selectCardPayment,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
});
function CardPaymentModalContainer({
cardPaymentModal,
toggleModalVisible,
bodyshop,
}) {
const { visible } = cardPaymentModal;
const { t } = useTranslation();
const handleCancel = () => {
toggleModalVisible();
};
const handleOK = () => {
toggleModalVisible();
};
return (
<Modal
open={visible}
onOk={handleOK}
onCancel={handleCancel}
footer={[
<Button key="back" onClick={handleCancel}>
{t("job_payments.buttons.goback")}
</Button>,
]}
width="80%"
destroyOnClose
>
<CardPaymentModalComponent />
</Modal>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(CardPaymentModalContainer);

View File

@@ -1,5 +1,5 @@
import Icon from "@ant-design/icons"; import Icon from "@ant-design/icons";
import { Tooltip } from "antd"; import { Spin, Tooltip } from "antd";
import i18n from "i18next"; import i18n from "i18next";
import moment from "moment"; import moment from "moment";
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
@@ -12,6 +12,8 @@ import {
} from "react-virtualized"; } from "react-virtualized";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import "./chat-message-list.styles.scss"; import "./chat-message-list.styles.scss";
import { useState } from "react";
import axios from "axios";
export default function ChatMessageListComponent({ messages }) { export default function ChatMessageListComponent({ messages }) {
const virtualizedListRef = useRef(null); const virtualizedListRef = useRef(null);
@@ -95,9 +97,7 @@ const MessageRender = (message) => {
key={idx} key={idx}
style={{ display: "flex", justifyContent: "center" }} style={{ display: "flex", justifyContent: "center" }}
> >
<a href={i} target="__blank"> <ImageDisplay src={i} />
<img alt="Received" className="message-img" src={i} />
</a>
</div> </div>
))} ))}
<div>{message.text}</div> <div>{message.text}</div>
@@ -116,3 +116,21 @@ const StatusRender = (status) => {
return null; return null;
} }
}; };
const ImageDisplay = ({ src }) => {
const [state, setstate] = useState({ loading: true, url: null });
useEffect(() => {
axios
.post("/sms/fetchmedia", { mediaUrl: src })
.then(({ data }) => setstate({ loading: false, url: data }));
}, [src]);
if (state.loading === true) return <Spin />;
return (
<a href={i} target="__blank">
<img alt="Received" className="message-img" src={state.url} />
</a>
);
};

View File

@@ -8,23 +8,15 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
searchingForConversation: searchingForConversation,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
}); });
export function ChatOpenButton({ export function ChatOpenButton({ bodyshop, phone, jobid, openChatByPhone }) {
bodyshop,
searchingForConversation,
phone,
jobid,
openChatByPhone,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
if (!phone) return <></>; if (!phone) return <></>;
@@ -37,7 +29,7 @@ export function ChatOpenButton({
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
const p = parsePhoneNumber(phone, "CA"); const p = parsePhoneNumber(phone, "CA");
if (searchingForConversation) return; //This is to prevent finding the same thing twice.
if (p && p.isValid()) { if (p && p.isValid()) {
openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid }); openChatByPhone({ phone_num: p.formatInternational(), jobid: jobid });
} else { } else {

View File

@@ -59,14 +59,6 @@ export default function ContractsCarsComponent({
sortOrder: sortOrder:
state.sortedInfo.columnKey === "model" && state.sortedInfo.order, state.sortedInfo.columnKey === "model" && state.sortedInfo.order,
}, },
{
title: t("courtesycars.fields.color"),
dataIndex: "color",
key: "color",
sorter: (a, b) => alphaSort(a.color, b.color),
sortOrder:
state.sortedInfo.columnKey === "color" && state.sortedInfo.order,
},
{ {
title: t("courtesycars.fields.plate"), title: t("courtesycars.fields.plate"),
dataIndex: "plate", dataIndex: "plate",
@@ -101,9 +93,6 @@ export default function ContractsCarsComponent({
(cc.model || "") (cc.model || "")
.toLowerCase() .toLowerCase()
.includes(state.search.toLowerCase()) || .includes(state.search.toLowerCase()) ||
(cc.color || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(cc.plate || "").toLowerCase().includes(state.search.toLowerCase()) (cc.plate || "").toLowerCase().includes(state.search.toLowerCase())
); );

View File

@@ -2,10 +2,10 @@ import { useQuery } from "@apollo/client";
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ContractJobsComponent from "./contract-jobs.component"; import ContractJobsComponent from "./contract-jobs.component";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -15,7 +15,6 @@ export function ContractJobsContainer({ selectedJobState, bodyshop }) {
const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, { const { loading, error, data } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: { variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"], statuses: bodyshop.md_ro_statuses.active_statuses || ["Open"],
isConverted: true,
}, },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",

View File

@@ -4,14 +4,14 @@ import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import { setModalContext } from "../../redux/modals/modals.actions";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container"; import ContractsFindModalContainer from "../contracts-find-modal/contracts-find-modal.container";
import { setModalContext } from "../../redux/modals/modals.actions";
import moment from "moment";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import moment from "moment";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({

View File

@@ -34,18 +34,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
{/* <FormFieldsChanged form={form} /> */} {/* <FormFieldsChanged form={form} /> */}
<LayoutFormRow header={t("courtesycars.labels.vehicle")}> <LayoutFormRow header={t("courtesycars.labels.vehicle")}>
<Form.Item
label={t("courtesycars.fields.year")}
name="year"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item <Form.Item
label={t("courtesycars.fields.make")} label={t("courtesycars.fields.make")}
name="make" name="make"
@@ -70,6 +58,18 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item
label={t("courtesycars.fields.year")}
name="year"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item <Form.Item
label={t("courtesycars.fields.plate")} label={t("courtesycars.fields.plate")}
name="plate" name="plate"
@@ -118,7 +118,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
}, },
]} ]}
> >
<InputNumber min={0} precision={0} /> <InputNumber precision={0} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("courtesycars.fields.fleetnumber")} label={t("courtesycars.fields.fleetnumber")}
@@ -213,24 +213,49 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
> >
<CourtesyCarStatus /> <CourtesyCarStatus />
</Form.Item> </Form.Item>
<Form.Item
label={t("courtesycars.fields.nextservicekm")}
name="nextservicekm"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
<div> <div>
<Form.Item <Form.Item
label={t("courtesycars.fields.nextservicekm")} label={t("courtesycars.fields.nextservicedate")}
name="nextservicekm" name="nextservicedate"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
> >
<InputNumber min={0} precision={0} /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
shouldUpdate={(p, c) => shouldUpdate={(p, c) =>
p.mileage !== c.mileage || p.nextservicekm !== c.nextservicekm p.mileage !== c.mileage ||
p.nextservicedate !== c.nextservicedate ||
p.nextservicekm !== c.nextservicekm
} }
> >
{() => { {() => {
const nextservicedate = form.getFieldValue("nextservicedate");
const nextservicekm = form.getFieldValue("nextservicekm"); const nextservicekm = form.getFieldValue("nextservicekm");
const mileageOver = const mileageOver =
nextservicekm <= form.getFieldValue("mileage"); nextservicekm <= form.getFieldValue("mileage");
if (mileageOver) const dueForService =
nextservicedate && moment(nextservicedate).isBefore(moment());
if (mileageOver || dueForService)
return ( return (
<Space direction="vertical" style={{ color: "tomato" }}> <Space direction="vertical" style={{ color: "tomato" }}>
<span> <span>
@@ -238,36 +263,6 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
{t("contracts.labels.cardueforservice")} {t("contracts.labels.cardueforservice")}
</span> </span>
<span>{`${nextservicekm} km`}</span> <span>{`${nextservicekm} km`}</span>
</Space>
);
return <></>;
}}
</Form.Item>
</div>
<div>
<Form.Item
label={t("courtesycars.fields.nextservicedate")}
name="nextservicedate"
>
<FormDatePicker />
</Form.Item>
<Form.Item
shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}
>
{() => {
const nextservicedate = form.getFieldValue("nextservicedate");
const dueForService =
nextservicedate &&
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
if (dueForService)
return (
<Space direction="vertical" style={{ color: "tomato" }}>
<span>
<WarningFilled style={{ marginRight: ".3rem" }} />
{t("contracts.labels.cardueforservice")}
</span>
<span> <span>
<DateFormatter>{nextservicedate}</DateFormatter> <DateFormatter>{nextservicedate}</DateFormatter>
</span> </span>
@@ -288,6 +283,12 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<Form.Item <Form.Item
label={t("courtesycars.fields.registrationexpires")} label={t("courtesycars.fields.registrationexpires")}
name="registrationexpires" name="registrationexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
> >
<FormDatePicker /> <FormDatePicker />
</Form.Item> </Form.Item>
@@ -299,8 +300,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
{() => { {() => {
const expires = form.getFieldValue("registrationexpires"); const expires = form.getFieldValue("registrationexpires");
const dateover = const dateover = expires && moment(expires).isBefore(moment());
expires && moment(expires).endOf("day").isBefore(moment());
if (dateover) if (dateover)
return ( return (
@@ -330,13 +330,14 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<FormDatePicker /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires} shouldUpdate={(p, c) =>
p.insuranceexpires !== c.insuranceexpires
}
> >
{() => { {() => {
const expires = form.getFieldValue("insuranceexpires"); const expires = form.getFieldValue("insuranceexpires");
const dateover = const dateover = expires && moment(expires).isBefore(moment());
expires && moment(expires).endOf("day").isBefore(moment());
if (dateover) if (dateover)
return ( return (

View File

@@ -9,15 +9,15 @@ import {
Table, Table,
Tooltip, Tooltip,
} from "antd"; } from "antd";
import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import moment from "moment";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
export default function CourtesyCarsList({ loading, courtesycars, refetch }) { export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
@@ -115,14 +115,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
sortOrder: sortOrder:
state.sortedInfo.columnKey === "model" && state.sortedInfo.order, state.sortedInfo.columnKey === "model" && state.sortedInfo.order,
}, },
{
title: t("courtesycars.fields.color"),
dataIndex: "color",
key: "color",
sorter: (a, b) => alphaSort(a.color, b.color),
sortOrder:
state.sortedInfo.columnKey === "color" && state.sortedInfo.order,
},
{ {
title: t("courtesycars.fields.plate"), title: t("courtesycars.fields.plate"),
dataIndex: "plate", dataIndex: "plate",
@@ -174,7 +166,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
(c.year || "").toLowerCase().includes(searchText.toLowerCase()) || (c.year || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.make || "").toLowerCase().includes(searchText.toLowerCase()) || (c.make || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.model || "").toLowerCase().includes(searchText.toLowerCase()) || (c.model || "").toLowerCase().includes(searchText.toLowerCase()) ||
(c.plate || "").toLowerCase().includes(searchText.toLowerCase()) ||
(t(c.status) || "").toLowerCase().includes(searchText.toLowerCase()) (t(c.status) || "").toLowerCase().includes(searchText.toLowerCase())
) )
: courtesycars; : courtesycars;

View File

@@ -23,40 +23,36 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
const appt = []; // Flatten Data const appt = []; // Flatten Data
data.scheduled_in_today.forEach((item) => { data.scheduled_in_today.forEach((item) => {
if (item.job) { var i = {
var i = { canceled: item.canceled,
canceled: item.canceled, id: item.id,
id: item.id, alt_transport: item.job.alt_transport,
alt_transport: item.job.alt_transport, clm_no: item.job.clm_no,
clm_no: item.job.clm_no, jobid: item.job.jobid,
jobid: item.job.jobid, ins_co_nm: item.job.ins_co_nm,
ins_co_nm: item.job.ins_co_nm, iouparent: item.job.iouparent,
iouparent: item.job.iouparent, ownerid: item.job.ownerid,
ownerid: item.job.ownerid, ownr_co_nm: item.job.ownr_co_nm,
ownr_co_nm: item.job.ownr_co_nm, ownr_ea: item.job.ownr_ea,
ownr_ea: item.job.ownr_ea, ownr_fn: item.job.ownr_fn,
ownr_fn: item.job.ownr_fn, ownr_ln: item.job.ownr_ln,
ownr_ln: item.job.ownr_ln, ownr_ph1: item.job.ownr_ph1,
ownr_ph1: item.job.ownr_ph1, ownr_ph2: item.job.ownr_ph2,
ownr_ph2: item.job.ownr_ph2, production_vars: item.job.production_vars,
production_vars: item.job.production_vars, ro_number: item.job.ro_number,
ro_number: item.job.ro_number, suspended: item.job.suspended,
suspended: item.job.suspended, v_make_desc: item.job.v_make_desc,
v_make_desc: item.job.v_make_desc, v_model_desc: item.job.v_model_desc,
v_model_desc: item.job.v_model_desc, v_model_yr: item.job.v_model_yr,
v_model_yr: item.job.v_model_yr, v_vin: item.job.v_vin,
v_vin: item.job.v_vin, vehicleid: item.job.vehicleid,
vehicleid: item.job.vehicleid, note: item.note,
note: item.note, start: moment(item.start).format("hh:mm a"),
start: moment(item.start).format("hh:mm a"), title: item.title,
title: item.title, };
}; appt.push(i);
appt.push(i);
}
});
appt.sort(function (a, b) {
return new moment(a.start) - new moment(b.start);
}); });
appt.sort ( function (a, b) { return new Date(a.start) - new Date(b.start); });
const columns = [ const columns = [
{ {
@@ -186,12 +182,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
}; };
return ( return (
<Card <Card title={t("dashboard.titles.scheduledintoday", {date: moment().startOf("day").format("MM/DD/YYYY")})} {...cardProps}>
title={t("dashboard.titles.scheduledintoday", {
date: moment().startOf("day").format("MM/DD/YYYY"),
})}
{...cardProps}
>
<div style={{ height: "100%" }}> <div style={{ height: "100%" }}>
<Table <Table
onChange={handleTableChange} onChange={handleTableChange}

View File

@@ -18,7 +18,7 @@ export default function DataLabel({
<div {...props} style={{ display: "flex" }}> <div {...props} style={{ display: "flex" }}>
<div <div
style={{ style={{
// flex: 2, flex: 2,
marginRight: ".2rem", marginRight: ".2rem",
}} }}
> >

View File

@@ -191,13 +191,6 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item
name="dms_model_override"
label={t("jobs.fields.dms.dms_model_override")}
initialValue={false}
>
<Switch />
</Form.Item>
</Space> </Space>
</div> </div>
)} )}

View File

@@ -109,7 +109,7 @@ export function EmailOverlayContainer({
] ]
: []), : []),
], ],
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.fullsize), media: selectedMedia.filter((m) => m.isSelected).map((m) => m.src),
//attachments, //attachments,
}); });
notification["success"]({ message: t("emails.successes.sent") }); notification["success"]({ message: t("emails.successes.sent") });

View File

@@ -54,7 +54,6 @@ export default function GlobalSearchOs() {
job.v_make_desc || "" job.v_make_desc || ""
} ${job.v_model_desc || ""}`}</span> } ${job.v_model_desc || ""}`}</span>
<span>{`${job.clm_no || ""}`}</span> <span>{`${job.clm_no || ""}`}</span>
<span>{`${job.plate_no || ""}`}</span>
</Space> </Space>
</Link> </Link>
), ),

View File

@@ -1,9 +1,10 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import Icon, { import Icon, {
BankFilled, BankFilled,
BarChartOutlined, BarChartOutlined,
CarFilled, CarFilled,
CheckCircleOutlined,
ClockCircleFilled, ClockCircleFilled,
CheckCircleOutlined,
DashboardFilled, DashboardFilled,
DollarCircleFilled, DollarCircleFilled,
ExportOutlined, ExportOutlined,
@@ -25,7 +26,6 @@ import Icon, {
UnorderedListOutlined, UnorderedListOutlined,
UserOutlined, UserOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Layout, Menu } from "antd"; import { Layout, Menu } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -70,8 +70,6 @@ const mapDispatchToProps = (dispatch) => ({
setReportCenterContext: (context) => setReportCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "reportCenter" })), dispatch(setModalContext({ context: context, modal: "reportCenter" })),
signOutStart: () => dispatch(signOutStart()), signOutStart: () => dispatch(signOutStart()),
setCardPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
}); });
function Header({ function Header({
@@ -85,7 +83,6 @@ function Header({
setPaymentContext, setPaymentContext,
setReportCenterContext, setReportCenterContext,
recentItems, recentItems,
setCardPaymentContext,
}) { }) {
const { Simple_Inventory } = useTreatments( const { Simple_Inventory } = useTreatments(
["Simple_Inventory"], ["Simple_Inventory"],
@@ -97,11 +94,6 @@ function Header({
{}, {},
bodyshop && bodyshop.imexshopid bodyshop && bodyshop.imexshopid
); );
const { ImEXPay } = useTreatments(
["ImEXPay"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -248,20 +240,6 @@ function Header({
> >
{t("menus.header.enterpayment")} {t("menus.header.enterpayment")}
</Menu.Item> </Menu.Item>
{ImEXPay.treatment === "on" && (
<Menu.Item
key="entercardpayments"
onClick={() => {
setCardPaymentContext({
actions: {},
context: {},
});
}}
icon={<Icon component={FaCreditCard} />}
>
{t("menus.header.entercardpayment")}
</Menu.Item>
)}
<Menu.Divider key="div5" /> <Menu.Divider key="div5" />
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}> <Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
<Link to="/manage/timetickets"> <Link to="/manage/timetickets">
@@ -274,11 +252,7 @@ function Header({
onClick={() => { onClick={() => {
setTimeTicketContext({ setTimeTicketContext({
actions: {}, actions: {},
context: { context: {},
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
},
}); });
}} }}
> >

View File

@@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR } from "../../graphql/vendors.queries"; import { SEARCH_VENDOR_AUTOCOMPLETE_WITH_ADDR } from "../../graphql/vendors.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
@@ -14,14 +13,13 @@ import VendorSearchSelect from "../vendor-search-select/vendor-search-select.com
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal); export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal);
export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) { export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
const [isModalVisible, setIsModalVisible] = useState(false); const [isModalVisible, setIsModalVisible] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -214,9 +212,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job, technician }) {
]} ]}
> >
<Radio.Group> <Radio.Group>
{!technician ? ( <Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
) : null}
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio> <Radio value={"p"}>{t("parts_orders.labels.print")}</Radio>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>

View File

@@ -4,7 +4,6 @@ import {
Divider, Divider,
Dropdown, Dropdown,
Form, Form,
Input,
Menu, Menu,
notification, notification,
Popover, Popover,
@@ -30,13 +29,11 @@ import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component"; import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleAtChange from "./job-at-change.component"; import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component"; import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component"; import ScheduleEventNote from "./schedule-event.note.component";
import { useMutation } from "@apollo/client"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -61,44 +58,16 @@ export function ScheduleEventComponent({
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const history = useHistory(); const history = useHistory();
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
const [title, setTitle] = useState(event.title);
const blockContent = (
<Space direction="vertical" wrap>
<Input
value={title}
onChange={(e) => setTitle(e.currentTarget.value)}
onBlur={async () => {
await updateAppointment({
variables: {
appid: event.id,
app: {
title: title,
},
},
optimisticResponse: {
update_appointments: {
__typename: "appointments_mutation_response",
returning: [
{
...event,
title: title,
__typename: "appointments",
},
],
},
},
});
}}
/>
const blockContent = (
<div>
<Button <Button
onClick={() => handleCancel({ id: event.id })} onClick={() => handleCancel({ id: event.id })}
disabled={event.arrived} disabled={event.arrived}
> >
{t("appointments.actions.unblock")} {t("appointments.actions.cancel")}
</Button> </Button>
</Space> </div>
); );
const popoverContent = ( const popoverContent = (
@@ -239,56 +208,46 @@ export function ScheduleEventComponent({
<Button>{t("appointments.actions.sendreminder")}</Button> <Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown> </Dropdown>
) : null} ) : null}
{event.arrived ? ( <Popover
trigger="click"
disabled={event.arrived}
content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button <Button
// onClick={() => handleCancel(event.id)} // onClick={() => handleCancel(event.id)}
disabled={event.arrived} disabled={event.arrived}
> >
{t("appointments.actions.cancel")} {t("appointments.actions.cancel")}
</Button> </Button>
) : ( </Popover>
<Popover
trigger="click"
disabled={event.arrived}
content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button
// onClick={() => handleCancel(event.id)}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
</Popover>
)}
{event.isintake ? ( {event.isintake ? (
<Button <Button
disabled={event.arrived} disabled={event.arrived}

View File

@@ -2,16 +2,12 @@ import { useMutation } from "@apollo/client";
import { notification } from "antd"; import { notification } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useDispatch } from "react-redux";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries"; import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import ScheduleEventComponent from "./schedule-event.component"; import ScheduleEventComponent from "./schedule-event.component";
export default function ScheduleEventContainer({ bodyshop, event, refetch }) { export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
const dispatch = useDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID); const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
@@ -38,24 +34,16 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
const jobUpdate = await updateJob({ const jobUpdate = await updateJob({
variables: { variables: {
jobId: event.job.id, jobId: event.job.id,
job: { job: {
date_scheduled: null, date_scheduled: null,
scheduled_in: null, scheduled_in: null,
scheduled_completion: null, scheduled_completion: null,
lost_sale_reason, lost_sale_reason,
date_lost_sale: new Date(),
status: bodyshop.md_ro_statuses.default_imported, status: bodyshop.md_ro_statuses.default_imported,
}, },
}, },
}); });
if (!jobUpdate.errors) {
dispatch(
insertAuditTrail({
jobid: event.job.id,
operation: AuditTrailMapping.appointmentcancel(lost_sale_reason),
})
);
}
if (!!jobUpdate.errors) { if (!!jobUpdate.errors) {
notification["error"]({ notification["error"]({
message: t("jobs.errors.updating", { message: t("jobs.errors.updating", {

View File

@@ -1,6 +1,5 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Card, Form, Input, notification, Switch } from "antd"; import { Button, Card, Form, Input, notification, Switch } from "antd";
import moment from "moment-business-days";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -13,15 +12,16 @@ import {
MARK_LATEST_APPOINTMENT_ARRIVED, MARK_LATEST_APPOINTMENT_ARRIVED,
} from "../../../../graphql/appointments.queries"; } from "../../../../graphql/appointments.queries";
import { UPDATE_JOB } from "../../../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
import { insertAuditTrail } from "../../../../redux/application/application.actions";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../../../redux/user/user.selectors"; } from "../../../../redux/user/user.selectors";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
import ConfigFormComponents from "../../../config-form-components/config-form-components.component"; import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component"; import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
import moment from "moment-business-days";
import { insertAuditTrail } from "../../../../redux/application/application.actions";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -230,7 +230,6 @@ export function JobChecklistForm({
)), )),
scheduled_delivery: scheduled_delivery:
job.scheduled_delivery && moment(job.scheduled_delivery), job.scheduled_delivery && moment(job.scheduled_delivery),
production_vars: job.production_vars,
}), }),
...(type === "deliver" && { ...(type === "deliver" && {
removeFromProduction: true, removeFromProduction: true,

View File

@@ -401,7 +401,7 @@ export function JobLinesComponent({
const markedTypes = [e.key]; const markedTypes = [e.key];
if (e.key === "PAN") markedTypes.push("PAP"); if (e.key === "PAN") markedTypes.push("PAP");
if (e.key === "PAS") markedTypes.push("PASL"); if (e.key === "PAS") markedTypes.push("PASL");
setSelectedLines((selectedLines) => setSelectedLines(
_.uniq([ _.uniq([
...selectedLines, ...selectedLines,
...jobLines.filter((item) => markedTypes.includes(item.part_type)), ...jobLines.filter((item) => markedTypes.includes(item.part_type)),
@@ -614,17 +614,8 @@ export function JobLinesComponent({
onSelectAll: (selected, selectedRows, changeRows) => { onSelectAll: (selected, selectedRows, changeRows) => {
setSelectedLines(selectedRows); setSelectedLines(selectedRows);
}, },
onSelect: (record, selected, selectedRows, nativeEvent) => { onSelect: (record, selected, selectedRows, nativeEvent) =>
if (selected) { setSelectedLines(selectedRows),
setSelectedLines((selectedLines) =>
_.uniqBy([...selectedLines, record], "id")
);
} else {
setSelectedLines((selectedLines) =>
selectedLines.filter((l) => l.id !== record.id)
);
}
},
}} }}
/> />
</div> </div>

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { notification, Select } from "antd"; import { notification, Select } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useMutation } from "@apollo/client";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -56,10 +56,8 @@ export function JobLineLocationPopup({ bodyshop, jobline, disabled }) {
<LoadingSpinner loading={loading}> <LoadingSpinner loading={loading}>
<Select <Select
autoFocus autoFocus
allowClear
dropdownMatchSelectWidth={100} dropdownMatchSelectWidth={100}
value={location} value={location}
onClear={() => setLocation(null)}
onSelect={handleChange} onSelect={handleChange}
onBlur={handleSave} onBlur={handleSave}
> >

View File

@@ -8,7 +8,7 @@ export default function JobLinesBillRefernece({ jobline }) {
return ( return (
<div style={{ color: subletRequired && "tomato" }}> <div style={{ color: subletRequired && "tomato" }}>
{subletRequired && <WarningFilled />} {subletRequired && <WarningFilled />}
{`${billLine.actual_price.toFixed(2)} x ${billLine.quantity} (${ {`${(billLine.actual_price * billLine.quantity).toFixed(2)} (${
billLine.bill.vendor.name billLine.bill.vendor.name
} #${billLine.bill.invoice_number})`} } #${billLine.bill.invoice_number})`}
</div> </div>

View File

@@ -1,26 +1,19 @@
import { EditFilled } from "@ant-design/icons";
import { Button, Card, Space, Table } from "antd"; import { Button, Card, Space, Table } from "antd";
import { EditFilled } from "@ant-design/icons";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import {
openChatByPhone,
setMessage,
} from "../../redux/messaging/messaging.actions";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import PaymentExpandedRowComponent from "../payment-expanded-row/payment-expanded-row.component";
import PaymentsGenerateLink from "../payments-generate-link/payments-generate-link.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -30,34 +23,20 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPaymentContext: (context) => setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })), dispatch(setModalContext({ context: context, modal: "payment" })),
setCardPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)),
}); });
export function JobPayments({ export function JobPayments({
job, job,
jobRO, jobRO,
bodyshop, bodyshop,
setMessage,
openChatByPhone,
setPaymentContext, setPaymentContext,
setCardPaymentContext,
refetch, refetch,
}) { }) {
const { ImEXPay } = useTreatments(
["ImEXPay"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation(); const { t } = useTranslation();
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
filteredInfo: {}, filteredInfo: {},
}); });
const columns = [ const columns = [
{ {
title: t("payments.fields.date"), title: t("payments.fields.date"),
@@ -170,21 +149,6 @@ export function JobPayments({
title={t("payments.labels.title")} title={t("payments.labels.title")}
extra={ extra={
<Space wrap> <Space wrap>
{ImEXPay.treatment === "on" && (
<>
<Button
onClick={() =>
setCardPaymentContext({
actions: { refetch },
context: { jobid: job.id, balance },
})
}
>
{t("menus.header.entercardpayment")}
</Button>
<PaymentsGenerateLink job={job} />
</>
)}
<Button <Button
disabled={!job.converted} disabled={!job.converted}
onClick={() => onClick={() =>
@@ -196,7 +160,6 @@ export function JobPayments({
> >
{t("menus.header.enterpayment")} {t("menus.header.enterpayment")}
</Button> </Button>
<DataLabel <DataLabel
valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }} valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }}
label={t("payments.labels.balance")} label={t("payments.labels.balance")}
@@ -215,11 +178,6 @@ export function JobPayments({
scroll={{ scroll={{
x: true, x: true,
}} }}
expandable={{
expandedRowRender: (record) => (
<PaymentExpandedRowComponent record={record} bodyshop={bodyshop} />
),
}}
summary={() => ( summary={() => (
<> <>
<Table.Summary.Row> <Table.Summary.Row>

View File

@@ -33,9 +33,7 @@ const JobSearchSelect = (
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE); useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
const executeSearch = (v) => { const executeSearch = (v) => {
console.log(v); if (v && v !== "") callSearch(v);
if (v && v.variables?.search !== "" && v.variables.search.length >= 2)
callSearch(v);
}; };
const debouncedExecuteSearch = _.debounce(executeSearch, 500); const debouncedExecuteSearch = _.debounce(executeSearch, 500);

View File

@@ -1,19 +1,18 @@
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Form, notification } from "antd"; import { Button, Form, notification } from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import moment from "moment";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { DateTimeFormat } from "./../../utils/DateFormatter";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -39,8 +38,8 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
setLoading(true); setLoading(true);
const result = await updateJob({ const result = await updateJob({
variables: { jobId: job.id, job: values }, variables: { jobId: job.id, job: values },
refetchQueries: ["GET_JOB_BY_PK"], refetchQueries: ['GET_JOB_BY_PK'],
awaitRefetchQueries: true, awaitRefetchQueries:true
}); });
const changedAuditFields = form.getFieldsValue( const changedAuditFields = form.getFieldsValue(
@@ -54,7 +53,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
operation: AuditTrailMapping.admin_jobfieldchange( operation: AuditTrailMapping.admin_jobfieldchange(
key, key,
changedAuditFields[key] instanceof moment changedAuditFields[key] instanceof moment
? DateTimeFormat(changedAuditFields[key]) ? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
: changedAuditFields[key] : changedAuditFields[key]
), ),
}); });
@@ -127,10 +126,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
<Form.Item label={t("jobs.fields.actual_in")} name="actual_in"> <Form.Item label={t("jobs.fields.actual_in")} name="actual_in">
<DateTimePicker /> <DateTimePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t("jobs.fields.date_repairstarted")} name="date_repairstarted">
label={t("jobs.fields.date_repairstarted")}
name="date_repairstarted"
>
<DateTimePicker /> <DateTimePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
@@ -177,15 +173,6 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
> >
<DateTimePicker /> <DateTimePicker />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
<DateTimePicker />
</Form.Item>
<Form.Item
label={t("jobs.fields.date_lost_sale")}
name="date_lost_sale"
>
<DateTimePicker />
</Form.Item>
</LayoutFormRow> </LayoutFormRow>
</Form> </Form>

View File

@@ -1,18 +1,19 @@
import { gql, useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, notification } from "antd"; import { Button, notification } from "antd";
import { gql } from "@apollo/client";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import moment from "moment";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import moment from "moment";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -149,10 +150,6 @@ export function JobAdminMarkReexport({
if (!result.errors) { if (!result.errors) {
notification["success"]({ message: t("jobs.successes.save") }); notification["success"]({ message: t("jobs.successes.save") });
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.admin_jobuninvoice(),
});
} else { } else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.saving", { message: t("jobs.errors.saving", {

View File

@@ -33,9 +33,8 @@ export function JobsAdminUnvoid({
mutation UNVOID_JOB($jobId: uuid!) { mutation UNVOID_JOB($jobId: uuid!) {
update_jobs_by_pk(pk_columns: {id: $jobId}, _set: {voided: false, status: "${ update_jobs_by_pk(pk_columns: {id: $jobId}, _set: {voided: false, status: "${
bodyshop.md_ro_statuses.default_imported bodyshop.md_ro_statuses.default_imported
}", date_void: null}) { }"}) {
id id
date_void
voided voided
status status
} }

View File

@@ -3,7 +3,7 @@ import {
useApolloClient, useApolloClient,
useLazyQuery, useLazyQuery,
useMutation, useMutation,
useQuery, useQuery
} from "@apollo/client"; } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import { Col, notification, Row } from "antd"; import { Col, notification, Row } from "antd";
@@ -20,7 +20,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { import {
DELETE_AVAILABLE_JOB, DELETE_AVAILABLE_JOB,
QUERY_AVAILABLE_JOBS, QUERY_AVAILABLE_JOBS,
QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK, QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK
} from "../../graphql/available-jobs.queries"; } from "../../graphql/available-jobs.queries";
import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries"; import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries";
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries"; import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
@@ -28,7 +28,7 @@ import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm"; import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
@@ -73,8 +73,6 @@ export function JobsAvailableContainer({
const [selectedJob, setSelectedJob] = useState(null); const [selectedJob, setSelectedJob] = useState(null);
const [selectedOwner, setSelectedOwner] = useState(null); const [selectedOwner, setSelectedOwner] = useState(null);
const [partsQueueToggle, setPartsQueueToggle] = useState(bodyshop.md_functionality_toggles.parts_queue_toggle);
const [insertLoading, setInsertLoading] = useState(false); const [insertLoading, setInsertLoading] = useState(false);
const [insertNote] = useMutation(INSERT_NEW_NOTE); const [insertNote] = useMutation(INSERT_NEW_NOTE);
@@ -96,7 +94,6 @@ export function JobsAvailableContainer({
logImEXEvent("job_import_new"); logImEXEvent("job_import_new");
setOwnerModalVisible(false); setOwnerModalVisible(false);
setInsertLoading(true); setInsertLoading(true);
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk); const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
@@ -123,7 +120,7 @@ export function JobsAvailableContainer({
let existingVehicles; let existingVehicles;
if (estData.est_data.v_vin) { if (estData.est_data.v_vin) {
//There's vehicle data, need to double-check the VIN. //There's vehicle data, need to double check the VIN.
existingVehicles = await client.query({ existingVehicles = await client.query({
query: SEARCH_VEHICLE_BY_VIN, query: SEARCH_VEHICLE_BY_VIN,
variables: { variables: {
@@ -138,7 +135,6 @@ export function JobsAvailableContainer({
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"), owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
job_totals: newTotals, job_totals: newTotals,
date_open: moment(), date_open: moment(),
status: bodyshop.md_ro_statuses.default_imported,
notes: { notes: {
data: { data: {
created_by: currentUser.email, created_by: currentUser.email,
@@ -146,7 +142,7 @@ export function JobsAvailableContainer({
text: t("jobs.labels.importnote"), text: t("jobs.labels.importnote"),
}, },
}, },
queued_for_parts: partsQueueToggle, queued_for_parts: true,
...(existingVehicles && existingVehicles.data.vehicles.length > 0 ...(existingVehicles && existingVehicles.data.vehicles.length > 0
? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null } ? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null }
: {}), : {}),
@@ -160,51 +156,46 @@ export function JobsAvailableContainer({
delete newJob.vehicle; delete newJob.vehicle;
} }
try { insertNewJob({
const r = await insertNewJob({ variables: {
variables: { job: newJob,
job: newJob, },
}, })
}); .then((r) => {
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
}
notification["success"]({
message: t("jobs.successes.created"),
onClick: () => {
history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`);
},
});
//Job has been inserted. Clean up the available jobs record.
if (CriticalPartsScanning.treatment === "on") { insertAuditTrail({
CriticalPartsScan(r.data.insert_jobs.returning[0].id); jobid: r.data.insert_jobs.returning[0].id,
} operation: AuditTrailMapping.jobimported(),
});
notification["success"]({ deleteJob({
message: t("jobs.successes.created"), variables: { id: estData.id },
onClick: () => { }).then((r) => {
history.push(`/manage/jobs/${r.data.insert_jobs.returning[0].id}`); refetch();
}, setInsertLoading(false);
}); });
//Job has been inserted. Clean up the available jobs record. })
.catch((r) => {
insertAuditTrail({ //error while inserting
jobid: r.data.insert_jobs.returning[0].id, notification["error"]({
operation: AuditTrailMapping.jobimported(), message: t("jobs.errors.creating", { error: r.message }),
}); });
deleteJob({
variables: { id: estData.id },
}).then((r) => {
refetch(); refetch();
setInsertLoading(false); setInsertLoading(false);
}); });
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
} catch (err) {
//error while inserting
notification["error"]({
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 //Suplement scenario
const onJobFindModalOk = async () => { const onJobFindModalOk = async () => {
logImEXEvent("job_import_supplement"); logImEXEvent("job_import_supplement");
@@ -256,14 +247,11 @@ export function JobsAvailableContainer({
// "0.00" // "0.00"
// ), // ),
// job_totals: newTotals, // job_totals: newTotals,
queued_for_parts: partsQueueToggle, // queued_for_parts: true,
}, },
}, },
}); });
if (CriticalPartsScanning.treatment === "on") {
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id); CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
} }
if (updateResult.errors) { if (updateResult.errors) {
@@ -338,14 +326,12 @@ export function JobsAvailableContainer({
const onOwnerModalCancel = () => { const onOwnerModalCancel = () => {
setOwnerModalVisible(false); setOwnerModalVisible(false);
setSelectedOwner(null); setSelectedOwner(null);
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
}; };
const onJobModalCancel = () => { const onJobModalCancel = () => {
setJobModalVisible(false); setJobModalVisible(false);
modalSearchState[1](""); modalSearchState[1]("");
setSelectedJob(null); setSelectedJob(null);
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
}; };
const addJobAsNew = (record) => { const addJobAsNew = (record) => {
@@ -366,8 +352,6 @@ export function JobsAvailableContainer({
}, [addJobAsSupp, availableJobId, clm_no]); }, [addJobAsSupp, availableJobId, clm_no]);
if (error) return <AlertComponent type="error" message={error.message} />; if (error) return <AlertComponent type="error" message={error.message} />;
return ( return (
<LoadingSpinner <LoadingSpinner
loading={insertLoading} loading={insertLoading}
@@ -377,14 +361,11 @@ export function JobsAvailableContainer({
loading={estDataRaw.loading} loading={estDataRaw.loading}
error={estDataRaw.error} error={estDataRaw.error}
owner={owner} owner={owner}
partsQueueToggle={partsQueueToggle}
setPartsQueueToggle={setPartsQueueToggle}
selectedOwner={selectedOwner} selectedOwner={selectedOwner}
setSelectedOwner={setSelectedOwner} setSelectedOwner={setSelectedOwner}
visible={ownerModalVisible} visible={ownerModalVisible}
onOk={onOwnerFindModalOk} onOk={onOwnerFindModalOk}
onCancel={onOwnerModalCancel} onCancel={onOwnerModalCancel}
/> />
<JobsFindModalContainer <JobsFindModalContainer
loading={estDataRaw.loading} loading={estDataRaw.loading}
@@ -396,8 +377,6 @@ export function JobsAvailableContainer({
onOk={onJobFindModalOk} onOk={onJobFindModalOk}
onCancel={onJobModalCancel} onCancel={onJobModalCancel}
modalSearchState={modalSearchState} modalSearchState={modalSearchState}
partsQueueToggle={partsQueueToggle}
setPartsQueueToggle={setPartsQueueToggle}
/> />
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>

View File

@@ -4,35 +4,22 @@ import axios from "axios";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { auth } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { useHistory } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updateJobCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
jobs(existingJobs = []) {
return existingJobs.filter(
(jobRef) => jobRef.__ref.includes(items) === false
);
},
},
});
}
export function JobsCloseExportButton({ export function JobsCloseExportButton({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -114,9 +101,6 @@ export function JobsCloseExportButton({
//Check to see if any of them failed. If they didn't don't execute the update. //Check to see if any of them failed. If they didn't don't execute the update.
const failedTransactions = PartnerResponse.data.filter((r) => !r.success); const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
const successfulTransactions = PartnerResponse.data.filter(
(r) => r.success
);
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
//Uh oh. At least one was no good. //Uh oh. At least one was no good.
failedTransactions.forEach((ft) => { failedTransactions.forEach((ft) => {
@@ -175,15 +159,12 @@ export function JobsCloseExportButton({
}, },
}); });
if (!!!jobUpdateResponse.errors) { if (!jobUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "jobsuccessexport", key: "jobsuccessexport",
message: t("jobs.successes.exported"), message: t("jobs.successes.exported"),
}); });
updateJobCache(
jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.exporting", { message: t("jobs.errors.exporting", {
@@ -192,31 +173,13 @@ export function JobsCloseExportButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
updateJobCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "jobid"
: "id"
]
)
),
]);
}
if (setSelectedJobs) { if (setSelectedJobs) {
setSelectedJobs((selectedJobs) => { setSelectedJobs((selectedJobs) => {
return selectedJobs.filter((i) => i !== jobId); return selectedJobs.filter((i) => i !== jobId);
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -108,7 +108,7 @@ export function JobsConvertButton({
}, },
]} ]}
> >
<Select showSearch> <Select>
{bodyshop.md_ins_cos.map((s, i) => ( {bodyshop.md_ins_cos.map((s, i) => (
<Select.Option key={i} value={s.name}> <Select.Option key={i} value={s.name}>
{s.name} {s.name}

View File

@@ -141,17 +141,6 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
<Form.Item label={t("jobs.fields.date_exported")} name="date_exported"> <Form.Item label={t("jobs.fields.date_exported")} name="date_exported">
<DateTimePicker disabled={true || jobRO} /> <DateTimePicker disabled={true || jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
<DateTimePicker disabled={true || jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.date_lost_sale")}
name="date_lost_sale"
>
<DateTimePicker disabled={true || jobRO} />
</Form.Item>
</FormRow> </FormRow>
</div> </div>
); );

View File

@@ -5,10 +5,10 @@ import {
Dropdown, Dropdown,
Form, Form,
Menu, Menu,
notification,
Popconfirm, Popconfirm,
Popover, Popover,
Select, Select,
notification,
} from "antd"; } from "antd";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -18,20 +18,18 @@ import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.queries"; import { CANCEL_APPOINTMENTS_BY_JOB_ID } from "../../graphql/appointments.queries";
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries"; import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent"; import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component"; import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import JobsDetailHeaderActionsExportcustdataComponent from "./jobs-detail-header-actions.exportcustdata.component"; import JobsDetailHeaderActionsExportcustdataComponent from "./jobs-detail-header-actions.exportcustdata.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -50,10 +48,6 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setModalContext({ context: context, modal: "jobCosting" })), dispatch(setModalContext({ context: context, modal: "jobCosting" })),
setTimeTicketContext: (context) => setTimeTicketContext: (context) =>
dispatch(setModalContext({ context: context, modal: "timeTicket" })), dispatch(setModalContext({ context: context, modal: "timeTicket" })),
setCardPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "cardPayment" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
}); });
export function JobsDetailHeaderActions({ export function JobsDetailHeaderActions({
@@ -67,8 +61,6 @@ export function JobsDetailHeaderActions({
setJobCostingContext, setJobCostingContext,
jobRO, jobRO,
setTimeTicketContext, setTimeTicketContext,
setCardPaymentContext,
insertAuditTrail,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
@@ -145,73 +137,63 @@ export function JobsDetailHeaderActions({
<Menu.Item <Menu.Item
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled} disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
> >
{job.status !== bodyshop.md_ro_statuses.default_scheduled ? ( <Popover
t("menus.jobsactions.cancelallappointments") trigger="click"
) : ( disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
<Popover content={
trigger="click" <Form
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled} layout="vertical"
content={ onFinish={async ({ lost_sale_reason }) => {
<Form const jobUpdate = await cancelAllAppointments({
layout="vertical" variables: {
onFinish={async ({ lost_sale_reason }) => { jobid: job.id,
const jobUpdate = await cancelAllAppointments({ job: {
variables: { date_scheduled: null,
jobid: job.id, scheduled_in: null,
job: { scheduled_completion: null,
date_scheduled: null, lost_sale_reason,
scheduled_in: null, status: bodyshop.md_ro_statuses.default_imported,
scheduled_completion: null,
lost_sale_reason,
date_lost_sale: new Date(),
status: bodyshop.md_ro_statuses.default_imported,
},
}, },
},
});
if (!jobUpdate.errors) {
notification["success"]({
message: t("appointments.successes.canceled"),
}); });
if (!jobUpdate.errors) { return;
notification["success"]({ }
message: t("appointments.successes.canceled"), }}
}); >
insertAuditTrail({ <Form.Item
jobid: job.id, name="lost_sale_reason"
operation: label={t("jobs.fields.lost_sale_reason")}
AuditTrailMapping.appointmentcancel(lost_sale_reason), rules={[
}); {
return; required: true,
} //message: t("general.validation.required"),
}} },
]}
> >
<Form.Item <Select
name="lost_sale_reason" options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label={t("jobs.fields.lost_sale_reason")} label: lsr,
rules={[ value: lsr,
{ }))}
required: true, />
//message: t("general.validation.required"), </Form.Item>
}, <Button
]} htmlType="submit"
> disabled={
<Select job.status !== bodyshop.md_ro_statuses.default_scheduled
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({ }
label: lsr, >
value: lsr, {t("appointments.actions.cancel")}
}))} </Button>
/> </Form>
</Form.Item> }
<Button >
htmlType="submit" {t("menus.jobsactions.cancelallappointments")}
disabled={ </Popover>
job.status !== bodyshop.md_ro_statuses.default_scheduled
}
>
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
{t("menus.jobsactions.cancelallappointments")}
</Popover>
)}
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
disabled={ disabled={
@@ -257,12 +239,7 @@ export function JobsDetailHeaderActions({
setTimeTicketContext({ setTimeTicketContext({
actions: {}, actions: {},
context: { context: { jobId: job.id },
jobId: job.id,
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
},
}); });
}} }}
> >
@@ -282,18 +259,6 @@ export function JobsDetailHeaderActions({
> >
{t("menus.header.enterpayment")} {t("menus.header.enterpayment")}
</Menu.Item> </Menu.Item>
<Menu.Item
key="entercardpayments"
disabled={!job.converted}
onClick={() => {
setCardPaymentContext({
actions: {},
context: { jobid: job.id },
});
}}
>
{t("menus.header.entercardpayment")}
</Menu.Item>
<Menu.Item key="cccontract" disabled={jobRO || !job.converted}> <Menu.Item key="cccontract" disabled={jobRO || !job.converted}>
<Link <Link
to={{ to={{
@@ -515,7 +480,6 @@ export function JobsDetailHeaderActions({
scheduled_in: null, scheduled_in: null,
scheduled_completion: null, scheduled_completion: null,
inproduction: false, inproduction: false,
date_void: new Date(),
}, },
note: [ note: [
{ {

View File

@@ -27,8 +27,6 @@ export default async function DuplicateJob(
delete existingJob.id; delete existingJob.id;
delete existingJob.createdat; delete existingJob.createdat;
delete existingJob.updatedat; delete existingJob.updatedat;
delete existingJob.cieca_stl;
delete existingJob.cieca_ttl;
const newJob = { const newJob = {
...existingJob, ...existingJob,
@@ -83,8 +81,6 @@ export async function CreateIouForJob(
delete existingJob.id; delete existingJob.id;
delete existingJob.createdat; delete existingJob.createdat;
delete existingJob.updatedat; delete existingJob.updatedat;
delete existingJob.cieca_stl;
delete existingJob.cieca_ttl;
const newJob = { const newJob = {
...existingJob, ...existingJob,

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { Gallery } from "react-grid-gallery"; import { Gallery } from "react-grid-gallery";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility"; import { GenerateThumbUrl } from "./job-documents.utility";
function JobsDocumentGalleryExternal({ function JobsDocumentGalleryExternal({
data, data,
@@ -15,7 +15,7 @@ function JobsDocumentGalleryExternal({
let documents = data.reduce((acc, value) => { let documents = data.reduce((acc, value) => {
if (value.type.startsWith("image")) { if (value.type.startsWith("image")) {
acc.push({ acc.push({
fullsize: GenerateSrcUrl(value), //src: GenerateSrcUrl(value),
src: GenerateThumbUrl(value), src: GenerateThumbUrl(value),
thumbnailHeight: 225, thumbnailHeight: 225,
thumbnailWidth: 225, thumbnailWidth: 225,

View File

@@ -52,7 +52,7 @@ function JobDocumentsLocalGalleryExternal({
val.type.mime && val.type.mime &&
val.type.mime.startsWith("image") val.type.mime.startsWith("image")
) { ) {
acc.push({ ...val, src: val.thumbnail, fullsize: val.src }); acc.push({ ...val, src: val.thumbnail });
} }
return acc; return acc;
}, []) }, [])

View File

@@ -13,26 +13,12 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updateJobCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
jobs(existingJobs = []) {
return existingJobs.filter(
(jobRef) => jobRef.__ref.includes(items) === false
);
},
},
});
}
export function JobsExportAllButton({ export function JobsExportAllButton({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -110,9 +96,7 @@ export function JobsExportAllButton({
Object.keys(groupedData).map(async (key) => { Object.keys(groupedData).map(async (key) => {
//Check to see if any of them failed. If they didn't don't execute the update. //Check to see if any of them failed. If they didn't don't execute the update.
const failedTransactions = groupedData[key].filter((r) => !r.success); const failedTransactions = groupedData[key].filter((r) => !r.success);
const successfulTransactions = groupedData[key].filter(
(r) => r.success
);
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
//Uh oh. At least one was no good. //Uh oh. At least one was no good.
failedTransactions.forEach((ft) => { failedTransactions.forEach((ft) => {
@@ -171,17 +155,12 @@ export function JobsExportAllButton({
}, },
}); });
if (!!!jobUpdateResponse.errors) { if (!jobUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "jobsuccessexport", key: "jobsuccessexport",
message: t("jobs.successes.exported"), message: t("jobs.successes.exported"),
}); });
updateJobCache(
jobUpdateResponse.data.update_jobs.returning.map(
(job) => job.id
)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.exporting", { message: t("jobs.errors.exporting", {
@@ -190,31 +169,14 @@ export function JobsExportAllButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "jobsuccessexport",
message: t("jobs.successes.exported"),
});
updateJobCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "jobid"
: "id"
]
)
),
]);
}
} }
}) })
); );
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
if (!!completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };

View File

@@ -14,8 +14,6 @@ export default function JobsFindModalComponent({
importOptionsState, importOptionsState,
modalSearchState, modalSearchState,
jobsListRefetch, jobsListRefetch,
partsQueueToggle,
setPartsQueueToggle,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [modalSearch, setModalSearch] = modalSearchState; const [modalSearch, setModalSearch] = modalSearchState;
@@ -201,12 +199,6 @@ export default function JobsFindModalComponent({
> >
{t("jobs.labels.override_header")} {t("jobs.labels.override_header")}
</Checkbox> </Checkbox>
<Checkbox
checked={partsQueueToggle}
onChange={(e) => setPartsQueueToggle(e.target.checked)}
>
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
</Checkbox>
</div> </div>
); );
} }

View File

@@ -24,8 +24,6 @@ export default connect(
setSelectedJob, setSelectedJob,
importOptionsState, importOptionsState,
modalSearchState, modalSearchState,
partsQueueToggle,
setPartsQueueToggle,
...modalProps ...modalProps
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -93,8 +91,6 @@ export default connect(
jobsListRefetch={jobsList.refetch} jobsListRefetch={jobsList.refetch}
jobsList={jobsData} jobsList={jobsData}
modalSearchState={modalSearchState} modalSearchState={modalSearchState}
partsQueueToggle={partsQueueToggle}
setPartsQueueToggle={setPartsQueueToggle}
/> />
) : null} ) : null}
</Modal> </Modal>

View File

@@ -1,8 +1,8 @@
import { import {
BranchesOutlined,
ExclamationCircleFilled, ExclamationCircleFilled,
PauseCircleOutlined, PauseCircleOutlined,
SyncOutlined, SyncOutlined,
BranchesOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
@@ -14,8 +14,8 @@ import { Link, useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries"; import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
@@ -46,7 +46,6 @@ export function JobsReadyList({ bodyshop }) {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: { variables: {
statuses: readyStatuses, statuses: readyStatuses,
isConverted: true,
}, },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",

View File

@@ -231,14 +231,7 @@ export function LaborAllocationsTable({
{summary.adjustments.toFixed(1)} {summary.adjustments.toFixed(1)}
</Table.Summary.Cell> </Table.Summary.Cell>
<Table.Summary.Cell> <Table.Summary.Cell>
<Typography.Text {summary.difference.toFixed(1)}
style={{
fontWeight: "bold",
color: summary.difference >= 0 ? "green" : "red",
}}
>
{summary.difference.toFixed(1)}
</Typography.Text>
</Table.Summary.Cell> </Table.Summary.Cell>
</Table.Summary.Row> </Table.Summary.Row>
)} )}

View File

@@ -6,7 +6,6 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters";
import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component"; import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -16,15 +15,6 @@ const mapStateToProps = createStructuredSelector({
function OwnerDetailJobsComponent({ bodyshop, owner }) { function OwnerDetailJobsComponent({ bodyshop, owner }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedJobs, setSelectedJobs] = useState([]); const [selectedJobs, setSelectedJobs] = useState([]);
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const columns = [ const columns = [
{ {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
@@ -36,9 +26,6 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
{record.ro_number || t("general.labels.na")} {record.ro_number || t("general.labels.na")}
</Link> </Link>
), ),
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.vehicle"), title: t("jobs.fields.vehicle"),
@@ -59,17 +46,11 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
title: t("jobs.fields.clm_no"), title: t("jobs.fields.clm_no"),
dataIndex: "clm_no", dataIndex: "clm_no",
key: "clm_no", key: "clm_no",
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
}, },
{ {
@@ -79,9 +60,6 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
render: (text, record) => ( render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter> <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
), ),
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
}, },
]; ];
@@ -102,7 +80,6 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
scroll={{ x: true }} scroll={{ x: true }}
rowKey="id" rowKey="id"
dataSource={owner.jobs} dataSource={owner.jobs}
onChange={handleTableChange}
rowSelection={{ rowSelection={{
onSelect: (record, selected, selectedRows) => { onSelect: (record, selected, selectedRows) => {
setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []); setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []);

View File

@@ -8,11 +8,10 @@ export default function OwnerFindModalComponent({
setSelectedOwner, setSelectedOwner,
ownersListLoading, ownersListLoading,
ownersList, ownersList,
partsQueueToggle,
setPartsQueueToggle,
}) { }) {
//setSelectedOwner is used to set the record id of the owner to use for adding the job. //setSelectedOwner is used to set the record id of the owner to use for adding the job.
const { t } = useTranslation(); const { t } = useTranslation();
const columns = [ const columns = [
{ {
title: t("owners.fields.ownr_ln"), title: t("owners.fields.ownr_ln"),
@@ -110,12 +109,6 @@ export default function OwnerFindModalComponent({
> >
{t("owners.labels.create_new")} {t("owners.labels.create_new")}
</Checkbox> </Checkbox>
<Checkbox
checked={partsQueueToggle}
onChange={(e) => setPartsQueueToggle(e.target.checked)}
>
{t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
</Checkbox>
</div> </div>
); );
} }

View File

@@ -14,8 +14,6 @@ export default function OwnerFindModalContainer({
owner, owner,
selectedOwner, selectedOwner,
setSelectedOwner, setSelectedOwner,
partsQueueToggle,
setPartsQueueToggle,
...modalProps ...modalProps
}) { }) {
//use owner object to run query and find what possible owners there are. //use owner object to run query and find what possible owners there are.
@@ -61,8 +59,6 @@ export default function OwnerFindModalContainer({
selectedOwner={selectedOwner} selectedOwner={selectedOwner}
setSelectedOwner={setSelectedOwner} setSelectedOwner={setSelectedOwner}
ownersListLoading={ownersList.loading} ownersListLoading={ownersList.loading}
partsQueueToggle={partsQueueToggle}
setPartsQueueToggle={setPartsQueueToggle}
ownersList={ ownersList={
ownersList.data && ownersList.data.search_owners ownersList.data && ownersList.data.search_owners
? ownersList.data.search_owners ? ownersList.data.search_owners

View File

@@ -21,8 +21,7 @@ const OwnerSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => {
useLazyQuery(SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE); useLazyQuery(SEARCH_OWNERS_BY_ID_FOR_AUTOCOMPLETE);
const executeSearch = (v) => { const executeSearch = (v) => {
if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch(v);
callSearch(v);
}; };
const debouncedExecuteSearch = _.debounce(executeSearch, 500); const debouncedExecuteSearch = _.debounce(executeSearch, 500);

View File

@@ -106,11 +106,7 @@ export default function OwnersListComponent({
<Input.Search <Input.Search
placeholder={search.search || t("general.labels.search")} placeholder={search.search || t("general.labels.search")}
onSearch={(value) => { onSearch={(value) => {
if (value?.length >= 3) { search.search = value;
search.search = value;
} else {
delete search.search;
}
history.push({ search: queryString.stringify(search) }); history.push({ search: queryString.stringify(search) });
}} }}
enterButton enterButton

View File

@@ -113,8 +113,6 @@ export function PartsOrderListTableComponent({
id: pol.id, id: pol.id,
line_desc: pol.line_desc, line_desc: pol.line_desc,
quantity: pol.quantity, quantity: pol.quantity,
act_price: pol.act_price,
oem_partno: pol.oem_partno,
}; };
}), }),
}, },

View File

@@ -79,20 +79,6 @@ export function PartsReceiveModalComponent({ bodyshop, form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item
label={t("joblines.fields.oem_partno")}
key={`${index}oem_partno`}
name={[field.name, "oem_partno"]}
>
<Input disabled />
</Form.Item>
<Form.Item
label={t("joblines.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<Input disabled />
</Form.Item>
<Form.Item <Form.Item
label={t("joblines.fields.location")} label={t("joblines.fields.location")}
key={`${index}location`} key={`${index}location`}

View File

@@ -1,39 +1,26 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, notification } from "antd"; import { Button, notification } from "antd";
import axios from "axios"; import axios from "axios";
import _ from "lodash";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { auth } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_BILLS } from "../../graphql/bills.queries"; import { UPDATE_BILLS } from "../../graphql/bills.queries";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient"; import { logImEXEvent } from "../../firebase/firebase.utils";
import _ from "lodash";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updateBillCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
bills(existingJobs = []) {
return existingJobs.filter(
(billRef) => billRef.__ref.includes(items) === false
);
},
},
});
}
export function PayableExportAll({ export function PayableExportAll({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -110,9 +97,7 @@ export function PayableExportAll({
proms.push( proms.push(
(async () => { (async () => {
const failedTransactions = groupedData[key].filter((r) => !r.success); const failedTransactions = groupedData[key].filter((r) => !r.success);
const successfulTransactions = groupedData[key].filter(
(r) => r.success
);
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
//Uh oh. At least one was no good. //Uh oh. At least one was no good.
failedTransactions.map((ft) => failedTransactions.map((ft) =>
@@ -158,15 +143,7 @@ export function PayableExportAll({
const billUpdateResponse = await updateBill({ const billUpdateResponse = await updateBill({
variables: { variables: {
billIdList: successfulTransactions.map( billIdList: [key],
(st) =>
st[
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo
? "billid"
: "id"
]
),
bill: { bill: {
exported: true, exported: true,
exported_at: new Date(), exported_at: new Date(),
@@ -179,11 +156,6 @@ export function PayableExportAll({
key: "billsuccessexport", key: "billsuccessexport",
message: t("bills.successes.exported"), message: t("bills.successes.exported"),
}); });
updateBillCache(
billUpdateResponse.data.update_bills.returning.map(
(bill) => bill.id
)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("bills.errors.exporting", { message: t("bills.errors.exporting", {
@@ -192,26 +164,6 @@ export function PayableExportAll({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "billsuccessexport",
message: t("bills.successes.exported"),
});
updateBillCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo
? "billid"
: "id"
]
)
),
]);
}
} }
})() })()
); );
@@ -220,6 +172,8 @@ export function PayableExportAll({
await Promise.all(proms); await Promise.all(proms);
if (!!completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -4,35 +4,22 @@ import axios from "axios";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { auth } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_BILLS } from "../../graphql/bills.queries"; import { UPDATE_BILLS } from "../../graphql/bills.queries";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updateBillCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
bills(existingJobs = []) {
return existingJobs.filter(
(billRef) => billRef.__ref.includes(items) === false
);
},
},
});
}
export function PayableExportButton({ export function PayableExportButton({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -172,11 +159,6 @@ export function PayableExportButton({
key: "billsuccessexport", key: "billsuccessexport",
message: t("bills.successes.exported"), message: t("bills.successes.exported"),
}); });
updateBillCache(
billUpdateResponse.data.update_bills.returning.map(
(bill) => bill.id
)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("bills.errors.exporting", { message: t("bills.errors.exporting", {
@@ -185,25 +167,7 @@ export function PayableExportButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
notification.open({
type: "success",
key: "billsuccessexport",
message: t("bills.successes.exported"),
});
updateBillCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "billid"
: "id"
]
)
),
]);
}
if (setSelectedBills) { if (setSelectedBills) {
setSelectedBills((selectedBills) => { setSelectedBills((selectedBills) => {

View File

@@ -1,186 +0,0 @@
import { useMutation, useQuery } from "@apollo/client";
import {
Button,
Descriptions,
InputNumber,
Modal,
Space,
notification,
} from "antd";
import axios from "axios";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import {
GET_REFUNDABLE_AMOUNT_BY_JOBID,
INSERT_PAYMENT_RESPONSE,
QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID,
} from "../../graphql/payment_response.queries";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
const { confirm } = Modal;
const openNotificationWithIcon = (type, t) => {
notification[type]({
message: t("job_payments.notifications.error.title"),
description: t("job_payments.notifications.error.description"),
});
};
const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
const [refundAmount, setRefundAmount] = useState(0);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation();
const { loading, error, data } = useQuery(
QUERY_PAYMENT_RESPONSE_BY_PAYMENT_ID,
{
variables: {
paymentid: record.id,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
},
}
);
const { data: refundable_amount, refetch } = useQuery(
GET_REFUNDABLE_AMOUNT_BY_JOBID,
{
variables: {
jobid: record.jobid,
},
}
);
const insertPayments = async (payment_response, refund_response) => {
await insertPayment({
variables: {
paymentInput: {
amount: -refund_response.data.amount,
transactionid: payment_response.response.receiptelements.transid,
payer: record.payer,
type: "Refund",
jobid: payment_response.jobid,
date: moment(Date.now()),
},
},
update(cache, { data }) {
cache.modify({
id: cache.identify({
id: payment_response.jobid,
__typename: "jobs",
}),
fields: {
payments(payments) {
return [...data.insert_payments.returning, ...payments];
},
},
});
},
});
await insertPaymentResponse({
variables: {
paymentResponse: {
amount: -refund_response.data.amount,
bodyshopid: payment_response.bodyshopid,
paymentid: payment_response.paymentid,
jobid: payment_response.jobid,
declinereason: "Refund",
ext_paymentid: payment_response.ext_paymentid,
successful: true,
response: refund_response.data,
},
},
});
};
const showConfirm = (payment_response) => {
confirm({
title: "Do you want to refund payment?",
content:
"The payment will be refunded. Click OK to confirm and Cancel to dismiss.",
async onOk() {
const refundResponse = await axios.post("/intellipay/payment_refund", {
bodyshop,
amount: refundAmount,
paymentid: payment_response.ext_paymentid,
});
if (refundResponse.data.status < 0) {
openNotificationWithIcon("error", t);
return;
}
insertPayments(payment_response, refundResponse);
// refetch refundable amount
refetch();
},
onCancel() {},
});
};
if (loading) return null;
if (error) return <p>Error loading data. Please Reload</p>;
const payment_response = data.payment_response[0];
const max_refundable_amount =
refundable_amount?.payment_response_aggregate.aggregate.sum.amount;
return (
<div>
<Descriptions
title={t("job_payments.titles.descriptions")}
contentStyle={{ fontWeight: "600" }}
column={4}
>
<Descriptions.Item label={t("job_payments.titles.payer")}>
{record.payer}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.payername")}>
{payment_response?.response?.nameOnCard ?? ""}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.amount")}>
<CurrencyFormatter>{record.amount}</CurrencyFormatter>
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.dateOfPayment")}>
{<DateTimeFormatter>{record.created_at}</DateTimeFormatter>}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.transactionid")}>
{record.transactionid}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymentid")}>
{payment_response?.response?.paymentreferenceid ?? ""}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymenttype")}>
{record.type}
</Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymentnum")}>
{record.paymentnum}
</Descriptions.Item>
{payment_response && (
<Descriptions.Item label={t("job_payments.titles.refundamount")}>
<Space>
<InputNumber
onChange={setRefundAmount}
max={max_refundable_amount}
min={0}
/>
<Button onClick={() => showConfirm(payment_response)}>
{t("job_payments.buttons.refundpayment")}
</Button>
</Space>
</Descriptions.Item>
)}
</Descriptions>
</div>
);
};
export default PaymentExpandedRowComponent;

View File

@@ -13,26 +13,12 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updatePaymentCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
payments(existingJobs = []) {
return existingJobs.filter(
(paymentRef) => paymentRef.__ref.includes(items) === false
);
},
},
});
}
export function PaymentExportButton({ export function PaymentExportButton({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -171,11 +157,6 @@ export function PaymentExportButton({
key: "paymentsuccessexport", key: "paymentsuccessexport",
message: t("payments.successes.exported"), message: t("payments.successes.exported"),
}); });
updatePaymentCache(
paymentUpdateResponse.data.update_payments.returning.map(
(payment) => payment.id
)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("payments.errors.exporting", { message: t("payments.errors.exporting", {
@@ -191,25 +172,7 @@ export function PaymentExportButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) { if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
notification.open({
type: "success",
key: "paymentsuccessexport",
message: t("payments.successes.exported"),
});
updatePaymentCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "paymentid"
: "id"
]
)
),
]);
}
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };

View File

@@ -13,25 +13,11 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
function updatePaymentCache(items) {
client.cache.modify({
id: "ROOT_QUERY",
fields: {
payments(existingJobs = []) {
return existingJobs.filter(
(paymentRef) => paymentRef.__ref.includes(items) === false
);
},
},
});
}
export function PaymentsExportAllButton({ export function PaymentsExportAllButton({
bodyshop, bodyshop,
currentUser, currentUser,
@@ -39,7 +25,7 @@ export function PaymentsExportAllButton({
disabled, disabled,
loadingCallback, loadingCallback,
completedCallback, completedCallback,
refetch, refetch
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updatePayments] = useMutation(UPDATE_PAYMENTS); const [updatePayments] = useMutation(UPDATE_PAYMENTS);
@@ -98,9 +84,7 @@ export function PaymentsExportAllButton({
proms.push( proms.push(
(async () => { (async () => {
const failedTransactions = groupedData[key].filter((r) => !r.success); const failedTransactions = groupedData[key].filter((r) => !r.success);
const successfulTransactions = groupedData[key].filter(
(r) => r.success
);
if (failedTransactions.length > 0) { if (failedTransactions.length > 0) {
//Uh oh. At least one was no good. //Uh oh. At least one was no good.
failedTransactions.map((ft) => failedTransactions.map((ft) =>
@@ -146,15 +130,7 @@ export function PaymentsExportAllButton({
}); });
const paymentUpdateResponse = await updatePayments({ const paymentUpdateResponse = await updatePayments({
variables: { variables: {
paymentIdList: successfulTransactions.map( paymentIdList: [key],
(st) =>
st[
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo
? "paymentid"
: "id"
]
),
payment: { payment: {
exportedat: new Date(), exportedat: new Date(),
}, },
@@ -166,11 +142,6 @@ export function PaymentsExportAllButton({
key: "paymentsuccessexport", key: "paymentsuccessexport",
message: t("payments.successes.exported"), message: t("payments.successes.exported"),
}); });
updatePaymentCache(
paymentUpdateResponse.data.update_payments.returning.map(
(payment) => payment.id
)
);
} else { } else {
notification["error"]({ notification["error"]({
message: t("payments.errors.exporting", { message: t("payments.errors.exporting", {
@@ -179,26 +150,6 @@ export function PaymentsExportAllButton({
}); });
} }
} }
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && successfulTransactions.length > 0) {
notification.open({
type: "success",
key: "paymentsuccessexport",
message: t("payments.successes.exported"),
});
updatePaymentCache([
...new Set(
successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo
? "paymentid"
: "id"
]
)
),
]);
}
} }
})() })()
); );
@@ -206,6 +157,7 @@ export function PaymentsExportAllButton({
await Promise.all(proms); await Promise.all(proms);
if (!!completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
setLoading(false); setLoading(false);
}; };

View File

@@ -1,156 +0,0 @@
import { CopyFilled } from "@ant-design/icons";
import { Button, Form, Popover, Space, message } from "antd";
import axios from "axios";
import Dinero from "dinero.js";
import { parsePhoneNumber } from "libphonenumber-js";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
openChatByPhone,
setMessage,
} from "../../redux/messaging/messaging.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(PaymentsGenerateLink);
export function PaymentsGenerateLink({
bodyshop,
callback,
job,
openChatByPhone,
setMessage,
}) {
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [paymentLink, setPaymentLink] = useState(null);
const handleFinish = async ({ amount }) => {
setLoading(true);
const p = parsePhoneNumber(job.ownr_ph1, "CA");
setLoading(true);
const response = await axios.post("/intellipay/generate_payment_url", {
bodyshop,
amount: amount,
account: job.ro_number,
invoice: job.id,
});
setLoading(false);
setPaymentLink(response.data.shorUrl);
openChatByPhone({
phone_num: p.formatInternational(),
jobid: job.id,
});
setMessage(
t("payments.labels.smspaymentreminder", {
shopname: bodyshop.shopname,
amount: amount,
payment_link: response.data.shorUrl,
})
);
//Add in confirmation & errors.
if (callback) callback();
// setVisible(false);
setLoading(false);
};
const popContent = (
<div>
<Form onFinish={handleFinish} layout="vertical" form={form}>
<Form.Item
label={t("payments.fields.amount")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name="amount"
>
<CurrencyFormItemComponent />
</Form.Item>
{paymentLink && (
<Space direction="vertical">
<Space
style={{ cursor: "pointer" }}
onClick={() => {
navigator.clipboard.writeText(paymentLink);
message.success(t("general.actions.copied"));
}}
>
<div
onClick={() => {
//Copy the link.
}}
>
{paymentLink}
</div>{" "}
<CopyFilled />
</Space>
<Button
onClick={() => {
const p = parsePhoneNumber(job.ownr_ph1, "CA");
openChatByPhone({
phone_num: p.formatInternational(),
jobid: job.id,
});
setMessage(
t("payments.labels.smspaymentreminder", {
shopname: bodyshop.shopname,
amount: Dinero({
amount: Math.round(form.getFieldValue("amount") * 100),
}).toFormat(),
payment_link: paymentLink,
})
);
}}
>
{t("general.actions.sendbysms")}
</Button>
</Space>
)}
</Form>
<Space>
<Button type="primary" onClick={() => form.submit()}>
{t("general.actions.submit")}
</Button>
<Button
onClick={() => {
form.resetFields();
setPaymentLink(null);
setVisible(false);
}}
>
{t("general.actions.cancel")}
</Button>
</Space>
</div>
);
return (
<Popover content={popContent} visible={visible}>
<Button onClick={() => setVisible(true)} loading={loading}>
{t("payments.actions.generatepaymentlink")}
</Button>
</Popover>
);
}

View File

@@ -5,13 +5,11 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions"; import { setEmailOptions } from "../../redux/email/email.actions";
import { selectPrintCenter } from "../../redux/modals/modals.selectors"; import { selectPrintCenter } from "../../redux/modals/modals.selectors";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
printCenterModal: selectPrintCenter, printCenterModal: selectPrintCenter,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)), setEmailOptions: (e) => dispatch(setEmailOptions(e)),
@@ -24,7 +22,6 @@ export function PrintCenterItemComponent({
id, id,
bodyshop, bodyshop,
disabled, disabled,
technician,
}) { }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { context } = printCenterModal; const { context } = printCenterModal;
@@ -47,24 +44,19 @@ export function PrintCenterItemComponent({
<Space wrap> <Space wrap>
{item.title} {item.title}
<PrinterOutlined onClick={renderToNewWindow} /> <PrinterOutlined onClick={renderToNewWindow} />
{!technician ? ( <MailOutlined
<MailOutlined onClick={() => {
onClick={() => { GenerateDocument(
GenerateDocument( {
{ name: item.key,
name: item.key, variables: { id: id },
variables: { id: id }, },
}, { to: context.job && context.job.ownr_ea, subject: item.subject },
{ "e",
to: context.job && context.job.ownr_ea, id
subject: item.subject, );
}, }}
"e", />
id
);
}}
/>
) : null}
{loading && <Spin />} {loading && <Spin />}
</Space> </Space>
</li> </li>

View File

@@ -20,9 +20,9 @@ import ProductionBoardCard from "../production-board-kanban-card/production-boar
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component"; import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component"; import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
//import "@asseinfo/react-kanban/dist/styles.css"; //import "@asseinfo/react-kanban/dist/styles.css";
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
import "./production-board-kanban.styles.scss"; import "./production-board-kanban.styles.scss";
import { createBoardData } from "./production-board-kanban.utils.js"; import { createBoardData } from "./production-board-kanban.utils.js";
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician, technician: selectTechnician,
@@ -153,18 +153,6 @@ export function ProductionBoardKanbanComponent({
0 0
) )
.toFixed(1); .toFixed(1);
const totalLAB = data
.reduce(
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const totalLAR = data
.reduce(
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1]) .filter((screen) => !!screen[1])
.slice(-1)[0]; .slice(-1)[0];
@@ -248,14 +236,6 @@ export function ProductionBoardKanbanComponent({
title={t("dashboard.titles.productionhours")} title={t("dashboard.titles.productionhours")}
value={totalHrs} value={totalHrs}
/> />
<Statistic
title={t("dashboard.titles.labhours")}
value={totalLAB}
/>
<Statistic
title={t("dashboard.titles.larhours")}
value={totalLAR}
/>
<Statistic <Statistic
title={t("appointments.labels.inproduction")} title={t("appointments.labels.inproduction")}
value={data && data.length} value={data && data.length}

View File

@@ -455,8 +455,8 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.order, state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name, bodyshop.employees.find((e) => e.id === a.employee_body)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name bodyshop.employees.find((e) => e.id === b.employee_body)?.first_name
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment <ProductionListEmployeeAssignment
@@ -474,8 +474,8 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.order, state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name, bodyshop.employees.find((e) => e.id === a.employee_prep)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name bodyshop.employees.find((e) => e.id === b.employee_prep)?.first_name
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment <ProductionListEmployeeAssignment
@@ -492,8 +492,8 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order, state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name, bodyshop.employees.find((e) => e.id === a.employee_csr)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name bodyshop.employees.find((e) => e.id === b.employee_csr)?.first_name
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment record={record} type="employee_csr" /> <ProductionListEmployeeAssignment record={record} type="employee_csr" />
@@ -508,9 +508,9 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.order, state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_refinish) bodyshop.employees.find((e) => e.id === a.employee_refinish)
?.first_name, ?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_refinish) bodyshop.employees.find((e) => e.id === b.employee_refinish)
?.first_name ?.first_name
), ),
render: (text, record) => ( render: (text, record) => (

View File

@@ -1,33 +1,31 @@
import { PrinterFilled } from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Button, Descriptions, Drawer, PageHeader, Space } from "antd"; import { Descriptions, Drawer, Space, PageHeader, Button } from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom"; import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries"; import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import StartChatButton from "../chat-open-button/chat-open-button.component"; import StartChatButton from "../chat-open-button/chat-open-button.component";
import JobAtChange from "../job-at-change/job-at-change.component";
import JobDetailCardsDocumentsComponent from "../job-detail-cards/job-detail-cards.documents.component"; import JobDetailCardsDocumentsComponent from "../job-detail-cards/job-detail-cards.documents.component";
import JobDetailCardsNotesComponent from "../job-detail-cards/job-detail-cards.notes.component"; import JobDetailCardsNotesComponent from "../job-detail-cards/job-detail-cards.notes.component";
import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.parts.component"; import JobDetailCardsPartsComponent from "../job-detail-cards/job-detail-cards.parts.component";
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container"; import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component"; import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
import JobAtChange from "../job-at-change/job-at-change.component";
import { PrinterFilled } from "@ant-design/icons";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) => setPrintCenterContext: (context) =>
@@ -42,7 +40,6 @@ export function ProductionListDetail({
bodyshop, bodyshop,
jobs, jobs,
setPrintCenterContext, setPrintCenterContext,
technician,
}) { }) {
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const history = useHistory(); const history = useHistory();
@@ -69,9 +66,7 @@ export function ProductionListDetail({
title={theJob.ro_number} title={theJob.ro_number}
extra={ extra={
<Space wrap> <Space wrap>
{!technician ? ( <ProductionRemoveButton jobId={theJob.id} />{" "}
<ProductionRemoveButton jobId={theJob.id} />
) : null}
<Button <Button
onClick={() => { onClick={() => {
setPrintCenterContext({ setPrintCenterContext({
@@ -87,9 +82,7 @@ export function ProductionListDetail({
<PrinterFilled /> <PrinterFilled />
{t("jobs.actions.printCenter")} {t("jobs.actions.printCenter")}
</Button> </Button>
{!technician ? ( <ScoreboardAddButton job={data ? data.jobs_by_pk : {}} />
<ScoreboardAddButton job={data ? data.jobs_by_pk : {}} />
) : null}
</Space> </Space>
} }
/> />

View File

@@ -55,27 +55,25 @@ export function ProductionListPrint({ bodyshop }) {
<Menu.SubMenu <Menu.SubMenu
title={t("reportcenter.templates.production_by_technician_one")} title={t("reportcenter.templates.production_by_technician_one")}
> >
{bodyshop.employees {bodyshop.employees.map((e) => (
.filter((e) => e.active) <Menu.Item
.map((e) => ( key={e.id}
<Menu.Item onClick={async () => {
key={e.id} setLoading(true);
onClick={async () => { await GenerateDocument(
setLoading(true); {
await GenerateDocument( name: production_by_technician_one.key,
{ variables: { id: e.id },
name: production_by_technician_one.key, },
variables: { id: e.id }, {},
}, "p"
{}, );
"p" setLoading(false);
); }}
setLoading(false); >
}} {e.first_name} {e.last_name}
> </Menu.Item>
{e.first_name} {e.last_name} ))}
</Menu.Item>
))}
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu <Menu.SubMenu
title={t("reportcenter.templates.production_by_category_one")} title={t("reportcenter.templates.production_by_category_one")}

View File

@@ -184,18 +184,6 @@ export function ProductionListTable({
0 0
) )
.toFixed(1); .toFixed(1);
const totalLAB = data
.reduce(
(acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const totalLAR = data
.reduce(
(acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
return ( return (
<div> <div>
<PageHeader <PageHeader
@@ -205,14 +193,6 @@ export function ProductionListTable({
title={t("dashboard.titles.productionhours")} title={t("dashboard.titles.productionhours")}
value={totalHrs} value={totalHrs}
/> />
<Statistic
title={t("dashboard.titles.labhours")}
value={totalLAB}
/>
<Statistic
title={t("dashboard.titles.larhours")}
value={totalLAR}
/>
<Statistic <Statistic
title={t("appointments.labels.inproduction")} title={t("appointments.labels.inproduction")}
value={dataSource && dataSource.length} value={dataSource && dataSource.length}

View File

@@ -13,29 +13,20 @@ import {
QUERY_APPOINTMENTS_BY_JOBID, QUERY_APPOINTMENTS_BY_JOBID,
} from "../../graphql/appointments.queries"; } from "../../graphql/appointments.queries";
import { QUERY_LBR_HRS_BY_PK, UPDATE_JOBS } from "../../graphql/jobs.queries"; import { QUERY_LBR_HRS_BY_PK, UPDATE_JOBS } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setEmailOptions } from "../../redux/email/email.actions"; import { setEmailOptions } from "../../redux/email/email.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectSchedule } from "../../redux/modals/modals.selectors"; import { selectSchedule } from "../../redux/modals/modals.selectors";
import { import { selectBodyshop } from "../../redux/user/user.selectors";
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { DateTimeFormat } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import ScheduleJobModalComponent from "./schedule-job-modal.component"; import ScheduleJobModalComponent from "./schedule-job-modal.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
scheduleModal: selectSchedule, scheduleModal: selectSchedule,
currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("schedule")), toggleModalVisible: () => dispatch(toggleModalVisible("schedule")),
setEmailOptions: (e) => dispatch(setEmailOptions(e)), setEmailOptions: (e) => dispatch(setEmailOptions(e)),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
}); });
export function ScheduleJobModalContainer({ export function ScheduleJobModalContainer({
@@ -43,8 +34,6 @@ export function ScheduleJobModalContainer({
bodyshop, bodyshop,
toggleModalVisible, toggleModalVisible,
setEmailOptions, setEmailOptions,
currentUser,
insertAuditTrail,
}) { }) {
const { visible, context, actions } = scheduleModal; const { visible, context, actions } = scheduleModal;
const { jobId, job, previousEvent } = context; const { jobId, job, previousEvent } = context;
@@ -133,22 +122,12 @@ export function ScheduleJobModalContainer({
end: moment(values.start).add(bodyshop.appt_length || 60, "minutes"), end: moment(values.start).add(bodyshop.appt_length || 60, "minutes"),
color: values.color, color: values.color,
note: values.note, note: values.note,
created_by: currentUser.email,
}, },
jobId: jobId, jobId: jobId,
altTransport: values.alt_transport, altTransport: values.alt_transport,
}, },
}); });
if (!appt.errors) {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.appointmentinsert(
DateTimeFormat(values.start)
),
});
}
if (!!appt.errors) { if (!!appt.errors) {
notification["error"]({ notification["error"]({
message: t("appointments.errors.saving", { message: t("appointments.errors.saving", {
@@ -170,7 +149,6 @@ export function ScheduleJobModalContainer({
scheduled_in: values.start, scheduled_in: values.start,
scheduled_completion: values.scheduled_completion, scheduled_completion: values.scheduled_completion,
lost_sale_reason: null, lost_sale_reason: null,
date_lost_sale: null,
}, },
}, },
}); });

View File

@@ -59,12 +59,11 @@ export function ScheduleManualEvent({ bodyshop, event }) {
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"], refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"],
}); });
} }
form.resetFields();
setVisibility(false);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
setLoading(false); setLoading(false);
setVisibility(false);
} }
}; };

View File

@@ -47,7 +47,9 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs, bodyhrs: dayAcc.bodyhrs + dayVal.bodyhrs,
painthrs: dayAcc.painthrs + dayVal.painthrs, painthrs: dayAcc.painthrs + dayVal.painthrs,
sales: sales:
dayAcc.sales + dayVal.job.job_totals.totals.subtotal.amount / 100, dayAcc.painthrs +
dayVal.job.job_totals.totals.subtotal.amount / 100 +
2500,
}; };
}, },
{ bodyhrs: 0, painthrs: 0, sales: 0 } { bodyhrs: 0, painthrs: 0, sales: 0 }

View File

@@ -8,45 +8,18 @@ export const CalculateWorkingDaysThisMonth = () => {
return moment().endOf("month").businessDaysIntoMonth(); return moment().endOf("month").businessDaysIntoMonth();
}; };
export const CalculateWorkingDaysInPeriod = (start, end) => {
return moment(start).businessDiff(moment(end));
};
export const CalculateWorkingDaysAsOfToday = () => { export const CalculateWorkingDaysAsOfToday = () => {
return moment().businessDaysIntoMonth(); return moment().businessDaysIntoMonth();
}; };
export const CalculateWorkingDaysLastMonth = () => {
return moment().subtract(1, "month").endOf("month").businessDaysIntoMonth();
};
export const WeeklyTargetHrs = (dailyTargetHrs, bodyshop) => { export const WeeklyTargetHrs = (dailyTargetHrs, bodyshop) => {
return ( return dailyTargetHrs * 5;
dailyTargetHrs *
CalculateWorkingDaysInPeriod(
moment().startOf("week"),
moment().endOf("week")
)
);
};
export const WeeklyTargetHrsInPeriod = (
dailyTargetHrs,
start,
end,
bodyshop
) => {
return dailyTargetHrs * CalculateWorkingDaysInPeriod(start, end);
}; };
export const MonthlyTargetHrs = (dailyTargetHrs, bodyshop) => { export const MonthlyTargetHrs = (dailyTargetHrs, bodyshop) => {
return dailyTargetHrs * CalculateWorkingDaysThisMonth(); return dailyTargetHrs * CalculateWorkingDaysThisMonth();
}; };
export const LastMonthTargetHrs = (dailyTargetHrs, bodyshop) => {
return dailyTargetHrs * CalculateWorkingDaysLastMonth();
};
export const AsOfTodayTargetHrs = (dailyTargetHrs, bodyshop) => { export const AsOfTodayTargetHrs = (dailyTargetHrs, bodyshop) => {
return dailyTargetHrs * CalculateWorkingDaysAsOfToday(); return dailyTargetHrs * CalculateWorkingDaysAsOfToday();
}; };

View File

@@ -1,26 +0,0 @@
const CustomTooltip = ({ active, payload, label }) => {
if (active && payload && payload.length) {
return (
<div
style={{
backgroundColor: "white",
border: "1px solid gray",
padding: "0.5rem",
}}
>
<p style={{ margin: "0" }}>{label}</p>
{payload.map((data, index) => {
return (
<p style={{ margin: "10px 0", color: data.color }} key={index}>{`${
data.name
} : ${data.value.toFixed(1)}`}</p>
);
})}
</div>
);
}
return null;
};
export default CustomTooltip;

View File

@@ -1,54 +0,0 @@
import { Card } from "antd";
import React from "react";
import {
Area,
CartesianGrid,
ComposedChart,
Legend,
Line,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import CustomTooltip from "./chart-custom-tooltip";
const graphProps = {
strokeWidth: 3,
};
export default function ScoreboardTimeTicketsChart({ data, chartTitle }) {
return (
<Card title={chartTitle}>
<ResponsiveContainer width="100%" height={275}>
<ComposedChart
data={data}
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
>
<CartesianGrid stroke="#f5f5f5" />
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
<YAxis yAxisId="left" strokeWidth={graphProps.strokeWidth} />
<Tooltip content={<CustomTooltip />} />
<Legend />
<Line
name="Target Hours"
type="monotone"
dataKey="accTargetHrs"
stroke="#ff7300"
yAxisId="left"
strokeWidth={graphProps.strokeWidth}
/>
<Area
type="monotone"
name="MTD Hours"
dataKey="accHrs"
fill="lightblue"
stroke="blue"
yAxisId="left"
/>
</ComposedChart>
</ResponsiveContainer>
</Card>
);
}

View File

@@ -1,393 +0,0 @@
import { useQuery } from "@apollo/client";
import { Col, Row } from "antd";
import _ from "lodash";
import moment from "moment";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
import ScoreboardTimeTicketsChart from "./scoreboard-timetickets.chart.component";
import ScoreboardTicketsStats from "./scoreboard-timetickets.stats.component";
import ScoreboardTimeticketsTargetsTable from "./scoreboard-timetickets.targets-table.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreboardTimeTicketsStats);
export function ScoreboardTimeTicketsStats({ bodyshop }) {
const { t } = useTranslation();
const startDate = moment().startOf("month")
const endDate = moment().endOf("month");
const fixedPeriods = useMemo(() => {
const endOfThisMonth = moment().endOf("month");
const startofthisMonth = moment().startOf("month");
const endOfLastmonth = moment().subtract(1, "month").endOf("month");
const startOfLastmonth = moment().subtract(1, "month").startOf("month");
const endOfThisWeek = moment().endOf("week");
const startOfThisWeek = moment().startOf("week");
const endOfLastWeek = moment().subtract(1, "week").endOf("week");
const startOfLastWeek = moment().subtract(1, "week").startOf("week");
const endOfPriorWeek = moment().subtract(2, "week").endOf("week");
const startOfPriorWeek = moment().subtract(2, "week").startOf("week");
const allDates = [
endOfThisMonth,
startofthisMonth,
endOfLastmonth,
startOfLastmonth,
endOfThisWeek,
startOfThisWeek,
endOfLastWeek,
startOfLastWeek,
endOfPriorWeek,
startOfPriorWeek,
];
const start = moment.min(allDates);
const end = moment.max(allDates);
return {
start,
end,
endOfThisMonth,
startofthisMonth,
endOfLastmonth,
startOfLastmonth,
endOfThisWeek,
startOfThisWeek,
endOfLastWeek,
startOfLastWeek,
endOfPriorWeek,
startOfPriorWeek,
};
}, []);
const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE_SB, {
variables: {
start: startDate.format("YYYY-MM-DD"),
end: endDate.format("YYYY-MM-DD"),
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
},
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
pollInterval: 60000,
skip: !fixedPeriods,
});
const calculatedData = useMemo(() => {
if (!data) return [];
const ret = {
totalThisWeek: 0,
totalThisWeekLAB: 0,
totalThisWeekLAR: 0,
totalLastWeek: 0,
totalLastWeekLAB: 0,
totalLastWeekLAR: 0,
totalPriorWeek: 0,
totalPriorWeekLAB: 0,
totalPriorWeekLAR: 0,
totalThisMonth: 0,
totalThisMonthLAB: 0,
totalThisMonthLAR: 0,
totalLastMonth: 0,
totalLastMonthLAB: 0,
totalLastMonthLAR: 0,
actualTotalOverPeriod: 0,
actualTotalOverPeriodLAB: 0,
actualTotalOverPeriodLAR: 0,
totalEffieciencyOverPeriod: 0,
totalEffieciencyOverPeriodLAB: 0,
totalEffieciencyOverPeriodLAR: 0,
seperatedThisWeek: {
sunday: {
total: 0,
lab: 0,
lar: 0,
},
monday: {
total: 0,
lab: 0,
lar: 0,
},
tuesday: {
total: 0,
lab: 0,
lar: 0,
},
wednesday: {
total: 0,
lab: 0,
lar: 0,
},
thursday: {
total: 0,
lab: 0,
lar: 0,
},
friday: {
total: 0,
lab: 0,
lar: 0,
},
saturday: {
total: 0,
lab: 0,
lar: 0,
},
},
};
data.fixedperiod.forEach((ticket) => {
const ticketDate = moment(ticket.date);
if (
ticketDate.isBetween(
fixedPeriods.startOfThisWeek,
fixedPeriods.endOfThisWeek,
undefined,
"[]"
)
) {
ret.totalThisWeek = ret.totalThisWeek + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
ret.totalThisWeekLAB = ret.totalThisWeekLAB + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
ret.totalThisWeekLAR = ret.totalThisWeekLAR + ticket.productivehrs;
//Seperate out to Day of Week
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].total =
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].total + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].lab =
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].lab + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].lar =
ret.seperatedThisWeek[
moment(ticket.date).format("dddd").toLowerCase()
].lar + ticket.productivehrs;
} else if (
ticketDate.isBetween(
fixedPeriods.startOfLastWeek,
fixedPeriods.endOfLastWeek,
undefined,
"[]"
)
) {
ret.totalLastWeek = ret.totalLastWeek + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
ret.totalLastWeekLAB = ret.totalLastWeekLAB + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
ret.totalLastWeekLAR = ret.totalLastWeekLAR + ticket.productivehrs;
} else if (
ticketDate.isBetween(
fixedPeriods.startOfPriorWeek,
fixedPeriods.endOfPriorWeek,
undefined,
"[]"
)
) {
ret.totalPriorWeek = ret.totalPriorWeek + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
ret.totalPriorWeekLAB = ret.totalPriorWeekLAB + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
ret.totalPriorWeekLAR = ret.totalPriorWeekLAR + ticket.productivehrs;
}
if (
ticketDate.isBetween(
fixedPeriods.startofthisMonth,
fixedPeriods.endOfThisMonth,
undefined,
"[]"
)
) {
ret.totalThisMonth = ret.totalThisMonth + ticket.productivehrs;
ret.actualTotalOverPeriod =
ret.actualTotalOverPeriod + (ticket.actualhrs || 0);
if (ticket.ciecacode !== "LAR") {
ret.totalThisMonthLAB = ret.totalThisMonthLAB + ticket.productivehrs;
ret.actualTotalOverPeriodLAB =
ret.actualTotalOverPeriodLAB + (ticket.actualhrs || 0);
}
if (ticket.ciecacode === "LAR") {
ret.totalThisMonthLAR = ret.totalThisMonthLAR + ticket.productivehrs;
ret.actualTotalOverPeriodLAR =
ret.actualTotalOverPeriodLAR + (ticket.actualhrs || 0);
}
} else if (
ticketDate.isBetween(
fixedPeriods.startOfLastmonth,
fixedPeriods.endOfLastmonth,
undefined,
"[]"
)
) {
ret.totalLastMonth = ret.totalLastMonth + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
ret.totalLastMonthLAB = ret.totalLastMonthLAB + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
ret.totalLastMonthLAR = ret.totalLastMonthLAR + ticket.productivehrs;
}
});
ret.totalEffieciencyOverPeriod = ret.actualTotalOverPeriod
? (ret.totalThisMonth / ret.actualTotalOverPeriod) * 100
: 0;
ret.totalEffieciencyOverPeriodLAB = ret.actualTotalOverPeriodLAB
? (ret.totalThisMonthLAB / ret.actualTotalOverPeriodLAB) * 100
: 0;
ret.totalEffieciencyOverPeriodLAR = ret.actualTotalOverPeriodLAR
? (ret.totalThisMonthLAR / ret.actualTotalOverPeriodLAR) * 100
: 0;
roundObject(ret);
const ticketsGroupedByDate = _.groupBy(data.timetickets, "date");
const listOfDays = Utils.ListOfDaysInCurrentMonth();
const combinedData = [],
labData = [],
larData = [];
var acc_comb = 0;
var acc_lab = 0;
var acc_lar = 0;
listOfDays.forEach((day) => {
const r = {
date: moment(day).format("MM/DD"),
actualhrs: 0,
productivehrs: 0,
};
const combined = {
accTargetHrs: _.round(
Utils.AsOfDateTargetHours(
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget,
day
) +
(bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget),
1
),
accHrs: 0,
};
const lab = {
accTargetHrs: _.round(
Utils.AsOfDateTargetHours(
bodyshop.scoreboard_target.dailyBodyTarget,
day
) + bodyshop.scoreboard_target.dailyBodyTarget,
1
),
accHrs: 0,
};
const lar = {
accTargetHrs: _.round(
Utils.AsOfDateTargetHours(
bodyshop.scoreboard_target.dailyPaintTarget,
day
) + bodyshop.scoreboard_target.dailyPaintTarget,
1
),
accHrs: 0,
};
if (ticketsGroupedByDate[day]) {
ticketsGroupedByDate[day].forEach((ticket) => {
r.actualhrs = r.actualhrs + ticket.actualhrs;
r.productivehrs = r.productivehrs + ticket.productivehrs;
acc_comb = acc_comb + ticket.productivehrs;
if (ticket.ciecacode !== "LAR")
acc_lab = acc_lab + ticket.productivehrs;
if (ticket.ciecacode === "LAR")
acc_lar = acc_lar + ticket.productivehrs;
});
}
combined.accHrs = acc_comb;
lab.accHrs = acc_lab;
lar.accHrs = acc_lar;
combinedData.push({ ...r, ...combined });
labData.push({ ...r, ...lab });
larData.push({ ...r, ...lar });
});
return {
fixed: ret,
combinedData: combinedData,
labData: labData,
larData: larData,
};
}, [fixedPeriods, data, bodyshop]);
if (error) return <AlertComponent message={error.message} type="error" />;
if (loading) return <LoadingSpinner />;
return (
<Row gutter={[16, 16]}>
<Col span={24}>
<ScoreboardTimeticketsTargetsTable />
</Col>
<Col span={24}>
<ScoreboardTicketsStats data={calculatedData.fixed} />
</Col>
<Col span={24}>
<ScoreboardTimeTicketsChart
data={calculatedData.combinedData}
chartTitle={t("scoreboard.labels.combinedcharttitle")}
/>
</Col>
<Col span={12}>
<ScoreboardTimeTicketsChart
data={calculatedData.labData}
chartTitle={t("scoreboard.labels.bodycharttitle")}
/>
</Col>
<Col span={12}>
<ScoreboardTimeTicketsChart
data={calculatedData.larData}
chartTitle={t("scoreboard.labels.refinishcharttitle")}
/>
</Col>
</Row>
);
}
function roundObject(inputObj) {
for (var key of Object.keys(inputObj)) {
if (typeof inputObj[key] === "number") {
inputObj[key] = inputObj[key].toFixed(1);
} else if (Array.isArray(inputObj[key])) {
inputObj[key].forEach((item) => roundObject(item));
} else if (typeof inputObj[key] === "object") {
roundObject(inputObj[key]);
}
}
}

View File

@@ -1,617 +0,0 @@
import {
Card,
Col,
Form,
Row,
Space,
Statistic,
Switch,
Typography,
} from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import * as Util from "../scoreboard-targets-table/scoreboard-targets-table.util";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreboardTicketsStats);
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(storedValue));
}, [key, storedValue]);
return [storedValue, setStoredValue];
}
export function ScoreboardTicketsStats({ data, bodyshop }) {
const { t } = useTranslation();
const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false);
const statisticSize = isLarge ? 36 : 24;
const statisticWeight = isLarge ? 550 : "normal";
const daySpan =
Util.CalculateWorkingDaysInPeriod(
moment().startOf("week"),
moment().endOf("week")
) > 5
? 3
: 4;
return (
<Card
title={t("scoreboard.labels.productivestatistics")}
extra={
<Form.Item
label={t("general.labels.tvmode")}
valuePropName="checked"
name={["tvmode"]}
>
<Switch
onClick={() => setIsLarge(!isLarge)}
defaultChecked={isLarge}
/>
</Form.Item>
}
>
<Row gutter={[16, 16]}>
<Col md={24}>
{/* Daily Stats */}
<Space direction="vertical" size="middle" style={{ display: "flex" }}>
<Row gutter={[16, 16]} align="center">
{[
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
].map((day) => {
if (bodyshop.workingdays[day] === true) {
return (
<Col key={day} span={daySpan} align="center">
<Card size="small" title={t("general.labels." + day)}>
<Row gutter={[8, 8]}>
<Col span={24}>
<Statistic
value={data.seperatedThisWeek[day].total}
valueStyle={{
color:
parseFloat(
data.seperatedThisWeek[day].total
) >=
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.seperatedThisWeek[day].lab}
valueStyle={{
color:
parseFloat(data.seperatedThisWeek[day].lab) >=
bodyshop.scoreboard_target.dailyBodyTarget
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.seperatedThisWeek[day].lar}
valueStyle={{
color:
parseFloat(data.seperatedThisWeek[day].lar) >=
bodyshop.scoreboard_target.dailyPaintTarget
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
);
} else {
return null;
}
})}
</Row>
{/* Weekly Stats */}
<Row gutter={[16, 16]}>
{/* This Week */}
<Col span={8} align="center">
<Card size="small" title={t("scoreboard.labels.thisweek")}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={data.totalThisWeek}
valueStyle={{
color:
parseFloat(data.totalThisWeek) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.totalThisWeekLAB}
valueStyle={{
color:
parseFloat(data.totalThisWeekLAB) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.totalThisWeekLAR}
valueStyle={{
color:
parseFloat(data.totalThisWeekLAR) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
{/* Last Week */}
<Col span={8} align="center">
<Card size="small" title={t("scoreboard.labels.lastweek")}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={data.totalLastWeek}
valueStyle={{
color:
parseFloat(data.totalLastWeek) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.totalLastWeekLAB}
valueStyle={{
color:
parseFloat(data.totalLastWeekLAB) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.totalLastWeekLAR}
valueStyle={{
color:
parseFloat(data.totalLastWeekLAR) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
{/* Prior Week */}
<Col span={8} align="center">
<Card size="small" title={t("scoreboard.labels.priorweek")}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={data.totalPriorWeek}
valueStyle={{
color:
parseFloat(data.totalPriorWeek) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.totalPriorWeekLAB}
valueStyle={{
color:
parseFloat(data.totalPriorWeekLAB) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.totalPriorWeekLAR}
valueStyle={{
color:
parseFloat(data.totalPriorWeekLAR) >=
Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
</Row>
{/* Monthly Stats */}
<Row gutter={[16, 16]}>
{/* This Month */}
<Col span={8} align="center">
<Card size="small" title={t("scoreboard.labels.thismonth")}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={data.totalThisMonth}
valueStyle={{
color:
parseFloat(data.totalThisMonth) >=
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.totalThisMonthLAB}
valueStyle={{
color:
parseFloat(data.totalThisMonthLAB) >=
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.totalThisMonthLAR}
valueStyle={{
color:
parseFloat(data.totalThisMonthLAR) >=
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
{/* Last Month */}
<Col span={8} align="center">
<Card size="small" title={t("scoreboard.labels.lastmonth")}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={data.totalLastMonth}
valueStyle={{
color:
parseFloat(data.totalLastMonth) >=
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={data.totalLastMonthLAB}
valueStyle={{
color:
parseFloat(data.totalLastMonthLAB) >=
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={data.totalLastMonthLAR}
valueStyle={{
color:
parseFloat(data.totalLastMonthLAR) >=
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
? "green"
: "red",
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
{/* Efficiency Over Period */}
<Col span={8} align="center">
<Card
size="small"
title={t("scoreboard.labels.efficiencyoverperiod")}
>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={`${data.totalEffieciencyOverPeriod || 0}%`}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.body")}
</Typography.Text>
}
value={`${data.totalEffieciencyOverPeriodLAB || 0}%`}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
<Col span={12}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.refinish")}
</Typography.Text>
}
value={`${data.totalEffieciencyOverPeriodLAR || 0}%`}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
</Row>
</Space>
{/* Disclaimer */}
<Typography.Text type="secondary">
*{t("scoreboard.labels.calendarperiod")}
</Typography.Text>
</Col>
</Row>
</Card>
);
}

View File

@@ -1,285 +0,0 @@
import { CalendarOutlined } from "@ant-design/icons";
import { Card, Col, Divider, Row, Statistic } from "antd";
import moment from "moment";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import * as Util from "../scoreboard-targets-table/scoreboard-targets-table.util";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
const rowGutter = [16, 16];
const statSpans = { xs: 24, sm: 3 };
export function ScoreboardTimeticketsTargetsTable({ bodyshop }) {
const { t } = useTranslation();
return (
<Card title={t("scoreboard.labels.targets")}>
<Row gutter={rowGutter}>
<Col xs={24} sm={{ offset: 0, span: 3 }} lg={{ span: 3 }}>
<Statistic
title={t("scoreboard.labels.workingdays")}
value={Util.CalculateWorkingDaysThisMonth()}
prefix={<CalendarOutlined />}
/>
</Col>
<Col xs={24} sm={{ offset: 0, span: 20 }} lg={{ offset: 0, span: 20 }}>
<Row>
<Col {...statSpans}>
<Statistic title="Type" value={t("scoreboard.labels.body")} />
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.dailytarget")}
value={bodyshop.scoreboard_target.dailyBodyTarget}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.thisweek")}
value={Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.lastweek")}
value={Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.priorweek")}
value={Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.thismonth")}
value={Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.lastmonth")}
value={Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.asoftodaytarget")}
value={Util.AsOfTodayTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
)}
/>
</Col>
</Row>
<Row>
<Col {...statSpans}>
<Statistic value={t("scoreboard.labels.refinish")} />
</Col>
<Col {...statSpans}>
<Statistic value={bodyshop.scoreboard_target.dailyPaintTarget} />
</Col>
<Col {...statSpans}>
<Statistic
value={Util.WeeklyTargetHrsInPeriod(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={Util.AsOfTodayTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)}
/>
</Col>
</Row>
<Row>
<Divider style={{ margin: 5 }} />
</Row>
<Row>
<Col {...statSpans}></Col>
<Col {...statSpans}>
<Statistic
value={(
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().startOf("week"),
moment().endOf("week"),
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(1, "week").startOf("week"),
moment().subtract(1, "week").endOf("week"),
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
) +
Util.WeeklyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
moment().subtract(2, "week").startOf("week"),
moment().subtract(2, "week").endOf("week"),
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.MonthlyTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.LastMonthTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
).toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
value={(
Util.AsOfTodayTargetHrs(
bodyshop.scoreboard_target.dailyBodyTarget,
bodyshop
) +
Util.AsOfTodayTargetHrs(
bodyshop.scoreboard_target.dailyPaintTarget,
bodyshop
)
).toFixed(1)}
/>
</Col>
</Row>
</Col>
</Row>
</Card>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreboardTimeticketsTargetsTable);

View File

@@ -1,14 +1,14 @@
import { useMutation, useQuery } from "@apollo/client";
import { Form, notification } from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import ShopInfoComponent from "./shop-info.component";
import { Form, notification } from "antd";
import { useQuery, useMutation } from "@apollo/client";
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { QUERY_BODYSHOP, UPDATE_SHOP } from "../../graphql/bodyshop.queries";
import AlertComponent from "../alert/alert.component";
import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; import FormsFieldChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import moment from "moment";
import ShopInfoComponent from "./shop-info.component";
export default function ShopInfoContainer() { export default function ShopInfoContainer() {
const [form] = Form.useForm(); const [form] = Form.useForm();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -52,28 +52,13 @@ export default function ShopInfoContainer() {
onFinish={handleFinish} onFinish={handleFinish}
initialValues={ initialValues={
data data
? data.bodyshops[0].accountingconfig.ClosingPeriod ? {
? { ...data.bodyshops[0],
...data.bodyshops[0], schedule_start_time: moment(
accountingconfig: { data.bodyshops[0].schedule_start_time
...data.bodyshops[0].accountingconfig, ),
ClosingPeriod: [ schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
moment(data.bodyshops[0].accountingconfig.ClosingPeriod[0]), }
moment(data.bodyshops[0].accountingconfig.ClosingPeriod[1]),
],
},
schedule_start_time: moment(
data.bodyshops[0].schedule_start_time
),
schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
}
: {
...data.bodyshops[0],
schedule_start_time: moment(
data.bodyshops[0].schedule_start_time
),
schedule_end_time: moment(data.bodyshops[0].schedule_end_time),
}
: null : null
} }
> >

View File

@@ -1,8 +1,6 @@
import { DeleteFilled } from "@ant-design/icons"; import { DeleteFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import { import {
Button, Button,
DatePicker,
Form, Form,
Input, Input,
InputNumber, InputNumber,
@@ -11,13 +9,8 @@ import {
Space, Space,
Switch, Switch,
} from "antd"; } from "antd";
import momentTZ from "moment-timezone";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import DatePickerRanges from "../../utils/DatePickerRanges";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component";
import PhoneFormItem, { import PhoneFormItem, {
@@ -25,23 +18,12 @@ import PhoneFormItem, {
} from "../form-items-formatted/phone-form-item.component"; } from "../form-items-formatted/phone-form-item.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import momentTZ from "moment-timezone";
const timeZonesList = momentTZ.tz.names(); const timeZonesList = momentTZ.tz.names();
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoGeneral);
export function ShopInfoGeneral({ form, bodyshop }) { export default function ShopInfoGeneral({ form }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { ClosingPeriod } = useTreatments(
["ClosingPeriod"],
{},
bodyshop && bodyshop.imexshopid
);
return ( return (
<div> <div>
@@ -410,20 +392,6 @@ export function ShopInfoGeneral({ form, bodyshop }) {
> >
<Select mode="tags" /> <Select mode="tags" />
</Form.Item> </Form.Item>
{ClosingPeriod.treatment === "on" && (
<>
<Form.Item
allowClear
name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
>
<DatePicker.RangePicker
format="MM/DD/YYYY"
ranges={DatePickerRanges}
/>
</Form.Item>
</>
)}
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow <LayoutFormRow
header={t("bodyshop.labels.scoreboardsetup")} header={t("bodyshop.labels.scoreboardsetup")}
@@ -634,20 +602,6 @@ export function ShopInfoGeneral({ form, bodyshop }) {
> >
<Select mode="tags" /> <Select mode="tags" />
</Form.Item> </Form.Item>
<Form.Item
name={["md_email_cc", "parts_return_slip"]}
label={t("bodyshop.fields.md_email_cc", {
template: "parts_return_slip",
})}
rules={[
{
//message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item <Form.Item
name={["tt_allow_post_to_invoiced"]} name={["tt_allow_post_to_invoiced"]}
label={t("bodyshop.fields.tt_allow_post_to_invoiced")} label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
@@ -681,13 +635,6 @@ export function ShopInfoGeneral({ form, bodyshop }) {
> >
<Select mode="tags" /> <Select mode="tags" />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.md_functionality_toggles.parts_queue_toggle")}
name={["md_functionality_toggles","parts_queue_toggle"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item <Form.Item
name={["last_name_first"]} name={["last_name_first"]}
label={t("bodyshop.fields.last_name_first")} label={t("bodyshop.fields.last_name_first")}

View File

@@ -175,19 +175,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
/> />
</Form.Item> </Form.Item>
)} )}
{bodyshop.pbs_serialnumber && (
<Form.Item
label={t("bodyshop.fields.dms.apcontrol")}
name={["pbs_configuration", "apcontrol"]}
>
<Select
options={[
{ value: "ro", label: "RO Number" },
{ value: "vendordmsid", label: "Vendor DMS ID" },
]}
/>
</Form.Item>
)}
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}> <LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}>
<Form.List name={["cdk_configuration", "payers"]}> <Form.List name={["cdk_configuration", "payers"]}>

View File

@@ -1,26 +1,21 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Card, Form, notification, Space } from "antd"; import { Button, Card, Form, notification, Space } from "antd";
import axios from "axios"; import axios from "axios";
import moment from "moment";
import momenttz from "moment-timezone";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries"; import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectTechnician } from "../../redux/tech/tech.selectors";
import { import { selectBodyshop } from "../../redux/user/user.selectors";
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
import TechClockInComponent from "./tech-job-clock-in-form.component"; import TechClockInComponent from "./tech-job-clock-in-form.component";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
import moment from "moment";
import { setModalContext } from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
technician: selectTechnician, technician: selectTechnician,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setTimeTicketContext: (context) => setTimeTicketContext: (context) =>
@@ -30,10 +25,9 @@ export function TechClockInContainer({
setTimeTicketContext, setTimeTicketContext,
technician, technician,
bodyshop, bodyshop,
currentUser,
}) { }) {
console.log( console.log(
"🚀 ~ file: tech-job-clock-in-form.container.jsx:30 ~ technician:", "🚀 ~ file: tech-job-clock-in-form.container.jsx:29 ~ technician:",
technician technician
); );
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -50,20 +44,14 @@ export function TechClockInContainer({
const handleFinish = async (values) => { const handleFinish = async (values) => {
setLoading(true); setLoading(true);
const theTime = (await axios.post("/utils/time")).data; const theTime = (await axios.post("/utils/time")).data;
const result = await insertTimeTicket({ const result = await insertTimeTicket({
variables: { variables: {
timeTicketInput: [ timeTicketInput: [
{ {
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
employeeid: technician.id, employeeid: technician.id,
date: date: moment(theTime).format("YYYY-MM-DD"),
typeof bodyshop.timezone === "string"
? momenttz.tz(theTime, bodyshop.timezone).format("YYYY-MM-DD")
: typeof bodyshop.timezone === "number"
? moment(theTime)
.format("YYYY-MM-DD")
.utcOffset(bodyshop.timezone)
: moment(theTime).format("YYYY-MM-DD"),
clockon: moment(theTime), clockon: moment(theTime),
jobid: values.jobid, jobid: values.jobid,
cost_center: values.cost_center, cost_center: values.cost_center,
@@ -78,12 +66,6 @@ export function TechClockInContainer({
values.cost_center values.cost_center
); );
}), }),
created_by: currentUser.email.concat(
" | ",
technician.employee_number
.concat(" ", technician.first_name, " ", technician.last_name)
.trim()
),
}, },
], ],
}, },
@@ -118,24 +100,13 @@ export function TechClockInContainer({
employeeid: technician.id, employeeid: technician.id,
flat_rate: emps.flat_rate, flat_rate: emps.flat_rate,
}, },
created_by: currentUser.email.concat(
" | ",
technician.employee_number
.concat(
" ",
technician.first_name,
" ",
technician.last_name
)
.trim()
),
}, },
}); });
}} }}
> >
{t("timetickets.actions.enter")} {t("timetickets.actions.enter")}
</Button> </Button>
<TechJobPrintTickets attendacePrint={false} /> <TechJobPrintTickets />
<Button <Button
type="primary" type="primary"
onClick={() => form.submit()} onClick={() => form.submit()}

View File

@@ -5,10 +5,10 @@ import {
Col, Col,
Form, Form,
InputNumber, InputNumber,
notification,
Popover, Popover,
Row, Row,
Select, Select,
notification,
} from "antd"; } from "antd";
import axios from "axios"; import axios from "axios";
import React, { useState } from "react"; import React, { useState } from "react";
@@ -16,14 +16,13 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
import { UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries"; import { UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
import TechJobClockoutDelete from "../tech-job-clock-out-delete/tech-job-clock-out-delete.component"; import TechJobClockoutDelete from "../tech-job-clock-out-delete/tech-job-clock-out-delete.component";
import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component"; import { LaborAllocationContainer } from "../time-ticket-modal/time-ticket-modal.component";
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -41,7 +40,6 @@ export function TechClockOffButton({
}) { }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET); const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET);
const [updateJobStatus] = useMutation(UPDATE_JOB_STATUS);
const [form] = Form.useForm(); const [form] = Form.useForm();
const { queryLoading, data: lineTicketData } = useQuery( const { queryLoading, data: lineTicketData } = useQuery(
GET_LINE_TICKET_BY_PK, GET_LINE_TICKET_BY_PK,
@@ -61,8 +59,7 @@ export function TechClockOffButton({
const handleFinish = async (values) => { const handleFinish = async (values) => {
logImEXEvent("tech_clock_out_job"); logImEXEvent("tech_clock_out_job");
const status = values.status;
delete values.status;
setLoading(true); setLoading(true);
const result = await updateTimeticket({ const result = await updateTimeticket({
variables: { variables: {
@@ -101,26 +98,6 @@ export function TechClockOffButton({
message: t("timetickets.successes.clockedout"), message: t("timetickets.successes.clockedout"),
}); });
} }
if (!isShiftTicket) {
const job_update_result = await updateJobStatus({
variables: {
jobId: jobId,
status: status,
},
});
if (!!job_update_result.errors) {
notification["error"]({
message: t("jobs.errors.updating", {
message: JSON.stringify(result.errors),
}),
});
} else {
notification["success"]({
message: t("jobs.successes.updated"),
});
}
}
setLoading(false); setLoading(false);
if (completedCallback) completedCallback(); if (completedCallback) completedCallback();
}; };
@@ -218,6 +195,7 @@ export function TechClockOffButton({
</Form.Item> </Form.Item>
</div> </div>
) : null} ) : null}
<Form.Item <Form.Item
name="cost_center" name="cost_center"
label={t("timetickets.fields.cost_center")} label={t("timetickets.fields.cost_center")}
@@ -250,29 +228,6 @@ export function TechClockOffButton({
</Select> </Select>
</Form.Item> </Form.Item>
{isShiftTicket ? (
<div></div>
) : (
<Form.Item
name="status"
label={t("jobs.fields.status")}
initialValue={
lineTicketData && lineTicketData.jobs_by_pk.status
}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select>
{bodyshop.md_ro_statuses.production_statuses.map((item) => (
<Select.Option key={item}></Select.Option>
))}
</Select>
</Form.Item>
)}
<Button type="primary" htmlType="submit" loading={loading}> <Button type="primary" htmlType="submit" loading={loading}>
{t("general.actions.save")} {t("general.actions.save")}
</Button> </Button>

View File

@@ -1,4 +1,4 @@
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd"; import { Button, Card, DatePicker, Form, Popover, Space } from "antd";
import moment from "moment"; import moment from "moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -21,13 +21,12 @@ export default connect(
mapDispatchToProps mapDispatchToProps
)(TechJobPrintTickets); )(TechJobPrintTickets);
export function TechJobPrintTickets({ technician, event, attendacePrint }) { export function TechJobPrintTickets({ technician, event }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false); const [visibility, setVisibility] = useState(false);
const Templates = TemplateList("report_center");
useEffect(() => { useEffect(() => {
if (visibility && event) { if (visibility && event) {
@@ -45,10 +44,7 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
try { try {
await GenerateDocument( await GenerateDocument(
{ {
name: name: TemplateList().timetickets_employee.key,
attendacePrint === true
? Templates.attendance_employee.key
: Templates.timetickets_employee.key,
variables: { variables: {
...(start ...(start
? { start: moment(start).startOf("day").format("YYYY-MM-DD") } ? { start: moment(start).startOf("day").format("YYYY-MM-DD") }
@@ -64,12 +60,9 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
}, },
{ {
to: technician.email, to: technician.email,
subject: subject: TemplateList().timetickets_employee.subject,
attendacePrint === true
? Templates.attendance_employee.subject
: Templates.timetickets_employee.subject,
}, },
values.sendby // === "email" ? "e" : "p" "p"
); );
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -99,25 +92,10 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
format={"MM/DD/YYYY"} format={"MM/DD/YYYY"}
/> />
</Form.Item> </Form.Item>
<Form.Item dependencies={["dates"]}>
{() => {
return (
<Form.Item
label={t("general.labels.sendby")}
name="sendby"
initialValue="p"
>
<Radio.Group>
<Radio value="e">{t("general.labels.email")}</Radio>
<Radio value="p">{t("general.labels.print")}</Radio>
</Radio.Group>
</Form.Item>
);
}}
</Form.Item>
<Space wrap> <Space wrap>
<Button type="primary" onClick={() => form.submit()}> <Button type="primary" onClick={() => form.submit()}>
{t("reportcenter.actions.generate")} {t("general.actions.print")}
</Button> </Button>
<Button <Button
onClick={() => { onClick={() => {
@@ -140,7 +118,7 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
return ( return (
<Popover content={overlay} visible={visibility}> <Popover content={overlay} visible={visibility}>
<Button loading={loading} onClick={handleClick}> <Button loading={loading} onClick={handleClick}>
{t("general.labels.reports")} {t("general.actions.print")}
</Button> </Button>
</Popover> </Popover>
); );

View File

@@ -25,7 +25,6 @@ export function TechLookupJobsList({ bodyshop }) {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: { variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
isConverted: true,
}, },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",

View File

@@ -9,10 +9,9 @@ import { createStructuredSelector } from "reselect";
import { import {
selectAuthLevel, selectAuthLevel,
selectBodyshop, selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import RbacWrapper, { import RbacWrapper, {
HasRbacAccess, HasRbacAccess,
@@ -21,7 +20,6 @@ import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
authLevel: selectAuthLevel, authLevel: selectAuthLevel,
currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -31,7 +29,6 @@ export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketList);
export function TimeTicketList({ export function TimeTicketList({
bodyshop, bodyshop,
authLevel, authLevel,
currentUser,
disabled, disabled,
loading, loading,
timetickets, timetickets,
@@ -196,15 +193,7 @@ export function TimeTicketList({
} }
}, },
}, },
{
title: t("timetickets.fields.created_by"),
dataIndex: "created_by",
key: "created_by",
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
sortOrder:
state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
render: (text, record) => record.created_by,
},
{ {
title: t("general.labels.actions"), title: t("general.labels.actions"),
dataIndex: "actions", dataIndex: "actions",
@@ -265,12 +254,7 @@ export function TimeTicketList({
(techConsole ? null : ( (techConsole ? null : (
<TimeTicketEnterButton <TimeTicketEnterButton
actions={{ refetch }} actions={{ refetch }}
context={{ context={{ jobId: jobId }}
jobId: jobId,
created_by: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
}}
disabled={disabled} disabled={disabled}
> >
{t("timetickets.actions.enter")} {t("timetickets.actions.enter")}

View File

@@ -1,5 +1,5 @@
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import { Button, Form, Modal, PageHeader, Space, notification } from "antd"; import { Button, Form, Modal, notification, PageHeader, Space } from "antd";
import moment from "moment"; import moment from "moment";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -77,7 +77,6 @@ export function TimeTicketModalContainer({
)[0].rate )[0].rate
: null, : null,
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
created_by: timeTicketModal.context.created_by,
}, },
], ],
}, },

View File

@@ -4,67 +4,48 @@ import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component"; import TechClockOffButton from "../tech-job-clock-out-button/tech-job-clock-out-button.component";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
export default function TimeTicketShiftActive({ export default function TimeTicketShiftActive({ timetickets, refetch }) {
timetickets,
refetch,
isTechConsole,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div> <div>
{timetickets.length > 0 ? ( {timetickets.length > 0 ? (
<div <div>
style={{ <Typography.Title level={2}>
display: "flex", {t("timetickets.labels.shiftalreadyclockedon")}
justifyContent: "space-between", </Typography.Title>
flexDirection: "column", <List
height: "100%", grid={{
}} gutter: 32,
> xs: 1,
<div style={{ display: "flex", justifyContent: "space-between" }}> sm: 2,
<Typography.Title level={2}> md: 3,
{t("timetickets.labels.shiftalreadyclockedon")} lg: 4,
</Typography.Title> xl: 5,
{isTechConsole ? ( xxl: 6,
<TechJobPrintTickets attendacePrint={true} /> }}
) : null} dataSource={timetickets || []}
</div> renderItem={(ticket) => (
<div style={{ flexGrow: 1 }}> <List.Item>
<List <Card
grid={{ title={t(ticket.memo)}
gutter: 32, actions={[
xs: 1, <TechClockOffButton
sm: 2, jobId={ticket.jobid}
md: 3, timeTicketId={ticket.id}
lg: 4, completedCallback={refetch}
xl: 5, isShiftTicket
xxl: 6, />,
}} ]}
dataSource={timetickets || []} >
renderItem={(ticket) => ( <DataLabel label={t("timetickets.fields.clockon")}>
<List.Item> <DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
<Card </DataLabel>
title={t(ticket.memo)} </Card>
actions={[ </List.Item>
<TechClockOffButton )}
jobId={ticket.jobid} ></List>
timeTicketId={ticket.id}
completedCallback={refetch}
isShiftTicket
/>,
]}
>
<DataLabel label={t("timetickets.fields.clockon")}>
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
</DataLabel>
</Card>
</List.Item>
)}
></List>
</div>
</div> </div>
) : null} ) : null}
</div> </div>

View File

@@ -1,8 +1,7 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Form, Space, notification } from "antd"; import { Button, Form, notification } from "antd";
import axios from "axios"; import axios from "axios";
import moment from "moment"; import moment from "moment";
import momenttz from "moment-timezone";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -13,7 +12,6 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
import TimeTicektShiftComponent from "./time-ticket-shift-form.component"; import TimeTicektShiftComponent from "./time-ticket-shift-form.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -31,10 +29,6 @@ export function TimeTicektShiftContainer({
isTechConsole, isTechConsole,
checkIfAlreadyClocked, checkIfAlreadyClocked,
}) { }) {
console.log(
"🚀 ~ file: time-ticket-shift-form.container.jsx:28 ~ technician:",
technician
);
const [form] = Form.useForm(); const [form] = Form.useForm();
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET); const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -69,30 +63,8 @@ export function TimeTicektShiftContainer({
employeeid: isTechConsole ? technician.id : employeeId, employeeid: isTechConsole ? technician.id : employeeId,
cost_center: "timetickets.labels.shift", cost_center: "timetickets.labels.shift",
clockon: theTime, clockon: theTime,
date: date: theTime,
typeof bodyshop.timezone === "string"
? momenttz.tz(theTime, bodyshop.timezone).format("YYYY-MM-DD")
: typeof bodyshop.timezone === "number"
? moment(theTime)
.utcOffset(bodyshop.timezone)
.format("YYYY-MM-DD")
: moment(theTime).format("YYYY-MM-DD"),
memo: values.memo, memo: values.memo,
created_by: isTechConsole
? currentUser.email.concat(
" | ",
technician.employee_number
.concat(
" ",
technician.first_name,
" ",
technician.last_name
)
.trim()
)
: currentUser.displayName
? currentUser.email.concat(" | ", currentUser.displayName)
: currentUser.email,
}, },
], ],
}, },
@@ -126,14 +98,9 @@ export function TimeTicektShiftContainer({
initialValues={{ cost_center: t("timetickets.labels.shift") }} initialValues={{ cost_center: t("timetickets.labels.shift") }}
> >
<TimeTicektShiftComponent form={form} /> <TimeTicektShiftComponent form={form} />
<Space wrap> <Button htmlType="submit" loading={loading}>
<Button htmlType="submit" loading={loading} type="primary"> {t("timetickets.actions.clockin")}
{t("timetickets.actions.clockin")} </Button>
</Button>
{isTechConsole === true ? (
<TechJobPrintTickets attendacePrint={true} />
) : null}
</Space>
</Form> </Form>
</div> </div>
); );

View File

@@ -76,7 +76,6 @@ export function TimeTicketShiftContainer({
<TimeTicketShiftActive <TimeTicketShiftActive
timetickets={data ? data.timetickets : []} timetickets={data ? data.timetickets : []}
refetch={refetch} refetch={refetch}
isTechConsole={isTechConsole}
/> />
) : ( ) : (
<TimeTicketShiftFormContainer <TimeTicketShiftFormContainer

View File

@@ -6,9 +6,8 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component"; import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -17,14 +16,6 @@ const mapStateToProps = createStructuredSelector({
export function VehicleDetailJobsComponent({ vehicle, bodyshop }) { export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedJobs, setSelectedJobs] = useState([]); const [selectedJobs, setSelectedJobs] = useState([]);
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: { text: "" },
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const columns = [ const columns = [
{ {
@@ -37,9 +28,6 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
{record.ro_number || t("general.labels.na")} {record.ro_number || t("general.labels.na")}
</Link> </Link>
), ),
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.owner"), title: t("jobs.fields.owner"),
@@ -55,17 +43,11 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
title: t("jobs.fields.clm_no"), title: t("jobs.fields.clm_no"),
dataIndex: "clm_no", dataIndex: "clm_no",
key: "clm_no", key: "clm_no",
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
}, },
{ {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
}, },
{ {
@@ -75,9 +57,6 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
render: (text, record) => ( render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter> <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
), ),
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
}, },
]; ];
@@ -97,7 +76,6 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
rowKey="id" rowKey="id"
scroll={{ x: true }} scroll={{ x: true }}
dataSource={vehicle.jobs} dataSource={vehicle.jobs}
onChange={handleTableChange}
rowSelection={{ rowSelection={{
onSelect: (record, selected, selectedRows) => { onSelect: (record, selected, selectedRows) => {
setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []); setSelectedJobs(selectedRows ? selectedRows.map((i) => i.id) : []);

View File

@@ -16,12 +16,13 @@ const VehicleSearchSelect = ({ value, onChange, onBlur, disabled }, ref) => {
SEARCH_VEHICLES_FOR_AUTOCOMPLETE SEARCH_VEHICLES_FOR_AUTOCOMPLETE
); );
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] = const [
useLazyQuery(SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE); callIdSearch,
{ loading: idLoading, error: idError, data: idData },
] = useLazyQuery(SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE);
const executeSearch = (v) => { const executeSearch = (v) => {
if (v && v.variables?.search !== "" && v.variables.search.length >= 2) callSearch(v);
callSearch(v);
}; };
const debouncedExecuteSearch = _.debounce(executeSearch, 500); const debouncedExecuteSearch = _.debounce(executeSearch, 500);

View File

@@ -154,6 +154,7 @@ export function VendorsFormComponent({
label={t("vendors.fields.phone")} label={t("vendors.fields.phone")}
name="phone" name="phone"
rules={[ rules={[
{ required: true, message: t("general.validation.required") },
({ getFieldValue }) => ({ getFieldValue }) =>
PhoneItemFormatterValidation(getFieldValue, "phone"), PhoneItemFormatterValidation(getFieldValue, "phone"),
]} ]}

View File

@@ -271,7 +271,6 @@ export const CANCEL_APPOINTMENTS_BY_JOB_ID = gql`
scheduled_completion scheduled_completion
status status
lost_sale_reason lost_sale_reason
date_lost_sale
} }
} }
`; `;

View File

@@ -5,7 +5,6 @@ export const INSERT_NEW_BILL = gql`
insert_bills(objects: $bill) { insert_bills(objects: $bill) {
returning { returning {
id id
invoice_number
} }
} }
} }

View File

@@ -39,7 +39,6 @@ export const QUERY_BODYSHOP = gql`
logo_img_path logo_img_path
md_ro_statuses md_ro_statuses
md_order_statuses md_order_statuses
md_functionality_toggles
shopname shopname
state state
state_tax_id state_tax_id
@@ -159,7 +158,6 @@ export const UPDATE_SHOP = gql`
logo_img_path logo_img_path
md_ro_statuses md_ro_statuses
md_order_statuses md_order_statuses
md_functionality_toggles
shopname shopname
state state
state_tax_id state_tax_id

View File

@@ -34,7 +34,6 @@ export const GET_LINE_TICKET_BY_PK = gql`
id id
lbr_adjustments lbr_adjustments
converted converted
status
} }
joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) { joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
id id
@@ -57,7 +56,6 @@ export const GET_LINE_TICKET_BY_PK = gql`
actualhrs actualhrs
ciecacode ciecacode
cost_center cost_center
created_by
date date
id id
jobid jobid
@@ -219,7 +217,6 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql`
id id
ro_number ro_number
} }
v_make_desc
} }
} }
`; `;

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