diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index b9079a9f0..815350435 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -17238,28 +17238,7 @@ - availablenew - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - - - availablesupplements + availablejobs false diff --git a/client/src/components/jobs-available-new/jobs-available-new.container.jsx b/client/src/components/jobs-available-new/jobs-available-new.container.jsx deleted file mode 100644 index b4545a946..000000000 --- a/client/src/components/jobs-available-new/jobs-available-new.container.jsx +++ /dev/null @@ -1,167 +0,0 @@ -import { useMutation, useQuery, useApolloClient } from "@apollo/react-hooks"; -import { notification } from "antd"; -import Axios from "axios"; -import Dinero from "dinero.js"; -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { connect } from "react-redux"; -import { useHistory } from "react-router-dom"; -import { createStructuredSelector } from "reselect"; -import { logImEXEvent } from "../../firebase/firebase.utils"; -import { - DELETE_ALL_AVAILABLE_NEW_JOBS, - QUERY_AVAILABLE_NEW_JOBS, -} from "../../graphql/available-jobs.queries"; -import { INSERT_NEW_JOB } from "../../graphql/jobs.queries"; -import { selectBodyshop } from "../../redux/user/user.selectors"; -import AlertComponent from "../alert/alert.component"; -import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -import JobsAvailableComponent from "./jobs-available-new.component"; -import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries"; - -const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, -}); - -export function JobsAvailableContainer({ - deleteJob, - estDataLazyLoad, - bodyshop, -}) { - const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_NEW_JOBS, { - fetchPolicy: "network-only", - }); - - const history = useHistory(); - const { t } = useTranslation(); - - const [modalVisible, setModalVisible] = useState(false); - const [selectedOwner, setSelectedOwner] = useState(null); - const [insertLoading, setInsertLoading] = useState(false); - const [deleteAllNewJobs] = useMutation(DELETE_ALL_AVAILABLE_NEW_JOBS); - const [insertNewJob] = useMutation(INSERT_NEW_JOB); - const client = useApolloClient(); - const [loadEstData, estData] = estDataLazyLoad; - - const onModalOk = async () => { - logImEXEvent("job_import_new"); - - setModalVisible(false); - setInsertLoading(true); - - if ( - !( - estData.data && - estData.data.available_jobs_by_pk && - estData.data.available_jobs_by_pk.est_data - ) - ) { - //We don't have the right data. Error! - setInsertLoading(false); - notification["error"]({ - message: t("jobs.errors.creating", { error: "No job data present." }), - }); - return; - } - - const newTotals = ( - await Axios.post("/job/totals", { - job: { - ...estData.data.available_jobs_by_pk.est_data, - joblines: estData.data.available_jobs_by_pk.est_data.joblines.data, - }, - }) - ).data; - - let existingVehicles; - if (estData.data.available_jobs_by_pk.est_data.vehicle) { - //There's vehicle data, need to double check the VIN. - existingVehicles = await client.query({ - query: SEARCH_VEHICLE_BY_VIN, - variables: { - vin: estData.data.available_jobs_by_pk.est_data.vehicle.data.v_vin, - }, - }); - } - - const newJob = { - ...estData.data.available_jobs_by_pk.est_data, - clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), - owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"), - job_totals: newTotals, - queued_for_parts: true, - ...(existingVehicles && existingVehicles.data.vehicles.length > 0 - ? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null } - : {}), - }; - - insertNewJob({ - variables: { - job: selectedOwner - ? Object.assign( - {}, - newJob, - { owner: null }, - { ownerid: selectedOwner } - ) - : newJob, - }, - }) - .then((r) => { - 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. - - deleteJob({ - variables: { id: estData.data.available_jobs_by_pk.id }, - }).then((r) => { - refetch(); - setInsertLoading(false); - }); - }) - .catch((r) => { - //error while inserting - notification["error"]({ - message: t("jobs.errors.creating", { error: r.message }), - }); - refetch(); - setInsertLoading(false); - }); - }; - - const onModalCancel = () => { - setModalVisible(false); - setSelectedOwner(null); - }; - - if (error) return ; - return ( - - - - ); -} -export default connect(mapStateToProps, null)(JobsAvailableContainer); diff --git a/client/src/components/jobs-available-supplement/jobs-available-supplement.component.jsx b/client/src/components/jobs-available-supplement/jobs-available-supplement.component.jsx deleted file mode 100644 index 22fc955e2..000000000 --- a/client/src/components/jobs-available-supplement/jobs-available-supplement.component.jsx +++ /dev/null @@ -1,248 +0,0 @@ -import { - DeleteFilled, - PlusCircleFilled, - SyncOutlined, -} from "@ant-design/icons"; -import { Button, notification, Table, Input } from "antd"; -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import CurrencyFormatter from "../../utils/CurrencyFormatter"; -import { TimeAgoFormatter } from "../../utils/DateFormatter"; -import { alphaSort } from "../../utils/sorters"; -import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container"; - -export default function JobsAvailableSupplementComponent({ - loading, - data, - refetch, - deleteJob, - onModalOk, - onModalCancel, - modalVisible, - setModalVisible, - selectedJob, - setSelectedJob, - deleteAllNewJobs, - loadEstData, - estData, - importOptionsState, - modalSearchState, -}) { - const { t } = useTranslation(); - const [searchText, setSearchText] = useState(""); - - const [state, setState] = useState({ - sortedInfo: {}, - filteredInfo: { text: "" }, - }); - - const handleTableChange = (pagination, filters, sorter) => { - setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); - }; - - const columns = [ - { - title: t("jobs.fields.cieca_id"), - dataIndex: "cieca_id", - key: "cieca_id", - //width: "8%", - // onFilter: (value, record) => record.ro_number.includes(value), - // filteredValue: state.filteredInfo.text || null, - sorter: (a, b) => alphaSort(a, b), - sortOrder: - state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.ro_number"), - dataIndex: "job_id", - key: "job_id", - //width: "8%", - // onFilter: (value, record) => record.ro_number.includes(value), - // filteredValue: state.filteredInfo.text || null, - sorter: (a, b) => alphaSort(a, b), - sortOrder: - state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order, - render: (text, record) => ( -
- {(record.job && record.job_ro_number) || t("general.labels.na")} -
- ), - }, - { - title: t("jobs.fields.owner"), - dataIndex: "ownr_name", - key: "ownr_name", - ellipsis: true, - sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - //width: "25%", - sortOrder: - state.sortedInfo.columnKey === "ownr_name" && state.sortedInfo.order, - }, - { - title: t("jobs.fields.vehicle"), - dataIndex: "vehicle_info", - key: "vehicle_info", - sorter: (a, b) => alphaSort(a.vehicle_info, b.vehicle_info), - sortOrder: - state.sortedInfo.columnKey === "vehicle_info" && state.sortedInfo.order, - //ellipsis: true - }, - { - title: t("jobs.fields.clm_no"), - dataIndex: "clm_no", - key: "clm_no", - sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), - sortOrder: - state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - //width: "12%", - //ellipsis: true - }, - { - title: t("jobs.fields.clm_total"), - dataIndex: "clm_amt", - key: "clm_amt", - sorter: (a, b) => a.clm_amt - b.clm_amt, - sortOrder: - state.sortedInfo.columnKey === "clm_amt" && state.sortedInfo.order, - render: (text, record) => ( - {record.clm_amt} - ), - //width: "12%", - //ellipsis: true - }, - { - title: t("jobs.fields.uploaded_by"), - dataIndex: "uploaded_by", - key: "uploaded_by", - sorter: (a, b) => alphaSort(a.uploaded_by, b.uploaded_by), - sortOrder: - state.sortedInfo.columnKey === "uploaded_by" && state.sortedInfo.order, - //width: "12%", - //ellipsis: true - }, - { - title: t("jobs.fields.updated_at"), - dataIndex: "updated_at", - key: "updated_at", - sorter: (a, b) => new Date(a.updated_at) - new Date(b.updated_at), - sortOrder: - state.sortedInfo.columnKey === "updated_at" && state.sortedInfo.order, - render: (text, record) => ( - {record.updated_at} - ), - //width: "12%", - //ellipsis: true - }, - { - title: t("general.labels.actions"), - key: "actions", - render: (text, record, index) => ( - - - - - ), - }, - ]; - - const handleDeleteAll = () => { - deleteAllNewJobs() - .then((r) => { - notification["success"]({ - message: t("jobs.successes.all_deleted", { - count: r.data.delete_available_jobs.affected_rows, - }), - }); - refetch(); - }) - .catch((r) => { - notification["error"]({ - message: t("jobs.errors.deleted") + " " + r.message, - }); - }); - }; - - const availableJobs = data - ? searchText - ? data.available_jobs.filter( - (j) => - (j.ownr_name || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.vehicle_info || "") - .toLowerCase() - .includes(searchText.toLowerCase()) || - (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) - ) - : data.available_jobs - : []; - - return ( -
- - { - return ( -
- {t("jobs.labels.availablesupplements")} - - -
- { - setSearchText(e.currentTarget.value); - }} - /> -
-
- ); - }} - size="small" - pagination={{ position: "top" }} - columns={columns} - rowKey="id" - dataSource={availableJobs} - onChange={handleTableChange} - /> - - ); -} diff --git a/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx b/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx deleted file mode 100644 index c796caf74..000000000 --- a/client/src/components/jobs-available-supplement/jobs-available-supplement.container.jsx +++ /dev/null @@ -1,174 +0,0 @@ -import { useApolloClient, useMutation, useQuery } from "@apollo/react-hooks"; -import { notification } from "antd"; -import Axios from "axios"; -import Dinero from "dinero.js"; -import gql from "graphql-tag"; -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { connect } from "react-redux"; -import { useHistory } from "react-router-dom"; -import { createStructuredSelector } from "reselect"; -import { logImEXEvent } from "../../firebase/firebase.utils"; -import { - DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS, - QUERY_AVAILABLE_SUPPLEMENT_JOBS, -} from "../../graphql/available-jobs.queries"; -import { UPDATE_JOB } from "../../graphql/jobs.queries"; -import { selectBodyshop } from "../../redux/user/user.selectors"; -import AlertComponent from "../alert/alert.component"; -import LoadingSpinner from "../loading-spinner/loading-spinner.component"; -import JobsAvailableSupplementComponent from "./jobs-available-supplement.component"; -import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util"; -import HeaderFields from "./jobs-available-supplement.headerfields"; - -const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, -}); - -export function JobsAvailableSupplementContainer({ - deleteJob, - estDataLazyLoad, - bodyshop, -}) { - const { loading, error, data, refetch } = useQuery( - QUERY_AVAILABLE_SUPPLEMENT_JOBS - ); - const { t } = useTranslation(); - const history = useHistory(); - const client = useApolloClient(); - const [deleteAllNewJobs] = useMutation(DELETE_ALL_AVAILABLE_SUPPLEMENT_JOBS); - - const [modalVisible, setModalVisible] = useState(false); - const [selectedJob, setSelectedJob] = useState(null); - const [insertLoading, setInsertLoading] = useState(false); - const modalSearchState = useState(""); - const [updateJob] = useMutation(UPDATE_JOB); - const [loadEstData, estData] = estDataLazyLoad; - const importOptionsState = useState({ overrideHeaders: false }); - const importOptions = importOptionsState[0]; - - const onModalOk = async () => { - logImEXEvent("job_import_supplement"); - - setModalVisible(false); - setInsertLoading(true); - - if ( - !( - estData.data && - estData.data.available_jobs_by_pk && - estData.data.available_jobs_by_pk.est_data - ) - ) { - //We don't have the right data. Error! - setInsertLoading(false); - notification["error"]({ - message: t("jobs.errors.creating", { error: "No job data present." }), - }); - } else { - //create upsert job - let supp = estData.data.available_jobs_by_pk.est_data; - - delete supp.owner; - delete supp.vehicle; - if (importOptions.overrideHeaders) { - HeaderFields.forEach((item) => delete supp[item]); - } - - const newTotals = ( - await Axios.post("/job/totals", { - job: { - ...estData.data.available_jobs_by_pk.est_data, - joblines: estData.data.available_jobs_by_pk.est_data.joblines.data, - }, - }) - ).data; - - let suppDelta = await GetSupplementDelta( - client, - selectedJob, - estData.data.available_jobs_by_pk.est_data.joblines.data - ); - - delete supp.joblines; - await client.mutate({ - mutation: gql` - ${suppDelta} - `, - }); - updateJob({ - variables: { - jobId: selectedJob, - job: { - ...supp, - clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), - owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat( - "0.00" - ), - job_totals: newTotals, - queued_for_parts: true, - }, - }, - }) - .then((r) => { - notification["success"]({ - message: t("jobs.successes.supplemented"), - onClick: () => { - history.push( - `/manage/jobs/${r.data.update_jobs.returning[0].id}` - ); - }, - }); - //Job has been inserted. Clean up the available jobs record. - deleteJob({ - variables: { id: estData.data.available_jobs_by_pk.id }, - }).then((r) => { - refetch(); - setInsertLoading(false); - }); - }) - .catch((r) => { - //error while inserting - notification["error"]({ - message: t("jobs.errors.creating", { error: r.message }), - }); - refetch(); - setInsertLoading(false); - }); - } - }; - - const onModalCancel = () => { - setModalVisible(false); - modalSearchState[1](""); - setSelectedJob(null); - }; - - if (error) return ; - return ( - - - - ); -} -export default connect(mapStateToProps, null)(JobsAvailableSupplementContainer); diff --git a/client/src/components/jobs-available-supplement/jobs-available-supplement.estlines.util.js b/client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js similarity index 91% rename from client/src/components/jobs-available-supplement/jobs-available-supplement.estlines.util.js rename to client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js index 5fb8ea92f..97a9a38cd 100644 --- a/client/src/components/jobs-available-supplement/jobs-available-supplement.estlines.util.js +++ b/client/src/components/jobs-available-table/jobs-available-supplement.estlines.util.js @@ -1,15 +1,17 @@ import { GET_JOB_LINES_BY_PK } from "../../graphql/jobs-lines.queries"; import gql from "graphql-tag"; +import _ from "lodash"; + export const GetSupplementDelta = async (client, jobId, newLines) => { console.log("-----Begin Supplement-----"); const { - data: { joblines: existingLines }, + data: { joblines: existingLinesFromDb }, } = await client.query({ query: GET_JOB_LINES_BY_PK, variables: { id: jobId }, }); - + const existingLines = _.cloneDeep(existingLinesFromDb); const linesToInsert = []; const linesToUpdate = []; @@ -56,11 +58,11 @@ export const GetSupplementDelta = async (client, jobId, newLines) => { }; const generateInsertQuery = (lineToInsert, index, jobId) => { - lineToInsert.jobid = jobId; return ` - insert_joblines${index}: insert_joblines(objects: ${JSON.stringify( - lineToInsert - ).replace(/"(\w+)"\s*:/g, "$1:")}) { + insert_joblines${index}: insert_joblines(objects: ${JSON.stringify({ + ...lineToInsert, + jobid: jobId, + }).replace(/"(\w+)"\s*:/g, "$1:")}) { returning { id } diff --git a/client/src/components/jobs-available-supplement/jobs-available-supplement.headerfields.js b/client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js similarity index 100% rename from client/src/components/jobs-available-supplement/jobs-available-supplement.headerfields.js rename to client/src/components/jobs-available-table/jobs-available-supplement.headerfields.js diff --git a/client/src/components/jobs-available-new/jobs-available-new.component.jsx b/client/src/components/jobs-available-table/jobs-available-table.component.jsx similarity index 64% rename from client/src/components/jobs-available-new/jobs-available-new.component.jsx rename to client/src/components/jobs-available-table/jobs-available-table.component.jsx index 3a09e3f56..af26848dc 100644 --- a/client/src/components/jobs-available-new/jobs-available-new.component.jsx +++ b/client/src/components/jobs-available-table/jobs-available-table.component.jsx @@ -1,31 +1,30 @@ import { DeleteFilled, + DownloadOutlined, PlusCircleFilled, SyncOutlined, } from "@ant-design/icons"; -import { Button, notification, Table, Input } from "antd"; +import { useMutation } from "@apollo/react-hooks"; +import { Button, Input, notification, Space, Table } from "antd"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { + DELETE_ALL_AVAILABLE_JOBS, + DELETE_AVAILABLE_JOB, +} from "../../graphql/available-jobs.queries"; import CurrencyFormatter from "../../utils/CurrencyFormatter"; import { TimeAgoFormatter } from "../../utils/DateFormatter"; import { alphaSort } from "../../utils/sorters"; -import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.container"; - export default function JobsAvailableComponent({ loading, data, refetch, - deleteJob, - deleteAllNewJobs, - onModalOk, - onModalCancel, - modalVisible, - setModalVisible, - selectedOwner, - setSelectedOwner, - loadEstData, - estData, + addJobAsNew, + addJobAsSupp, }) { + const [deleteAllAvailableJobs] = useMutation(DELETE_ALL_AVAILABLE_JOBS); + const [deleteJob] = useMutation(DELETE_AVAILABLE_JOB); const { t } = useTranslation(); const [searchText, setSearchText] = useState(""); const [state, setState] = useState({ @@ -38,24 +37,41 @@ export default function JobsAvailableComponent({ }; const columns = [ - // { - // title: t("jobs.fields.cieca_id"), - // dataIndex: "cieca_id", - // key: "cieca_id", - // //width: "8%", - // // onFilter: (value, record) => record.ro_number.includes(value), - // // filteredValue: state.filteredInfo.text || null, - // sorter: (a, b) => alphaSort(a, b), - // sortOrder: - // state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order, - // }, + { + title: t("jobs.fields.cieca_id"), + dataIndex: "cieca_id", + key: "cieca_id", + sorter: (a, b) => alphaSort(a, b), + sortOrder: + state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order, + }, + { + title: t("jobs.fields.ro_number"), + dataIndex: "job_id", + key: "job_id", + //width: "8%", + // onFilter: (value, record) => record.ro_number.includes(value), + // filteredValue: state.filteredInfo.text || null, + sorter: (a, b) => alphaSort(a, b), + sortOrder: + state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order, + render: (text, record) => + record.job ? ( + + {(record.job && record.job_ro_number) || t("general.labels.na")} + + ) : ( +
+ {(record.job && record.job_ro_number) || t("general.labels.na")} +
+ ), + }, { title: t("jobs.fields.owner"), dataIndex: "ownr_name", key: "ownr_name", ellipsis: true, sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), - //width: "25%", sortOrder: state.sortedInfo.columnKey === "ownr_name" && state.sortedInfo.order, }, @@ -66,7 +82,6 @@ export default function JobsAvailableComponent({ sorter: (a, b) => alphaSort(a.vehicle_info, b.vehicle_info), sortOrder: state.sortedInfo.columnKey === "vehicle_info" && state.sortedInfo.order, - //ellipsis: true }, { title: t("jobs.fields.clm_no"), @@ -75,8 +90,6 @@ export default function JobsAvailableComponent({ sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order, - //width: "12%", - //ellipsis: true }, { title: t("jobs.fields.ins_co_nm"), @@ -85,8 +98,6 @@ export default function JobsAvailableComponent({ sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order, - //width: "12%", - //ellipsis: true }, { title: t("jobs.fields.clm_total"), @@ -98,8 +109,6 @@ export default function JobsAvailableComponent({ render: (text, record) => ( {record.clm_amt} ), - //width: "12%", - //ellipsis: true }, { title: t("jobs.fields.uploaded_by"), @@ -108,8 +117,6 @@ export default function JobsAvailableComponent({ sorter: (a, b) => alphaSort(a.uploaded_by, b.uploaded_by), sortOrder: state.sortedInfo.columnKey === "uploaded_by" && state.sortedInfo.order, - //width: "12%", - //ellipsis: true }, { title: t("jobs.fields.updated_at"), @@ -121,14 +128,12 @@ export default function JobsAvailableComponent({ render: (text, record) => ( {record.updated_at} ), - //width: "12%", - //ellipsis: true }, { title: t("general.labels.actions"), key: "actions", render: (text, record) => ( - + - + + ), - //width: "12%", - //ellipsis: true }, ]; - const owner = - estData.data && - estData.data.available_jobs_by_pk && - estData.data.available_jobs_by_pk.est_data && - estData.data.available_jobs_by_pk.est_data.owner && - estData.data.available_jobs_by_pk.est_data.owner.data && - !estData.data.available_jobs_by_pk.issupplement - ? estData.data.available_jobs_by_pk.est_data.owner.data - : null; - const availableJobs = data ? searchText ? data.available_jobs.filter( @@ -182,24 +176,13 @@ export default function JobsAvailableComponent({ : []; return ( -
- - -
{ - return ( -
- {t("jobs.labels.availablenew")} +
{ + return ( +
+ + {t("jobs.labels.availablejobs")} -
- { - setSearchText(e.currentTarget.value); - }} - /> -
+
+
+ { + setSearchText(e.currentTarget.value); + }} + />
- ); - }} - size="small" - pagination={{ position: "top" }} - columns={columns} - rowKey="id" - dataSource={availableJobs} - onChange={handleTableChange} - /> -
+ + ); + }} + size="small" + pagination={{ position: "top" }} + columns={columns} + rowKey="id" + dataSource={availableJobs} + onChange={handleTableChange} + /> ); } diff --git a/client/src/components/jobs-available-table/jobs-available-table.container.jsx b/client/src/components/jobs-available-table/jobs-available-table.container.jsx new file mode 100644 index 000000000..dce8d4593 --- /dev/null +++ b/client/src/components/jobs-available-table/jobs-available-table.container.jsx @@ -0,0 +1,315 @@ +import { + useApolloClient, + useLazyQuery, + useMutation, + useQuery +} from "@apollo/react-hooks"; +import { notification } from "antd"; +import Axios from "axios"; +import Dinero from "dinero.js"; +import gql from "graphql-tag"; +import _ from "lodash"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useHistory } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { logImEXEvent } from "../../firebase/firebase.utils"; +import { + DELETE_AVAILABLE_JOB, + QUERY_AVAILABLE_JOBS, + QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK +} from "../../graphql/available-jobs.queries"; +import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries"; +import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +import AlertComponent from "../alert/alert.component"; +import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container"; +import LoadingSpinner from "../loading-spinner/loading-spinner.component"; +import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.container"; +import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util"; +import HeaderFields from "./jobs-available-supplement.headerfields"; +import JobsAvailableTableComponent from "./jobs-available-table.component"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); + +export function JobsAvailableContainer({ bodyshop }) { + const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, { + fetchPolicy: "network-only", + }); + + const history = useHistory(); + const { t } = useTranslation(); + + const [ownerModalVisible, setOwnerModalVisible] = useState(false); + const [jobModalVisible, setJobModalVisible] = useState(false); + + const [selectedJob, setSelectedJob] = useState(null); + const [selectedOwner, setSelectedOwner] = useState(null); + const [insertLoading, setInsertLoading] = useState(false); + const [deleteJob] = useMutation(DELETE_AVAILABLE_JOB); + const [updateJob] = useMutation(UPDATE_JOB); + + const [insertNewJob] = useMutation(INSERT_NEW_JOB); + const client = useApolloClient(); + + const estDataLazyLoad = useLazyQuery(QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK); + const [loadEstData, estData] = estDataLazyLoad; + + const importOptionsState = useState({ overrideHeaders: false }); + const importOptions = importOptionsState[0]; + const modalSearchState = useState(""); + + const onOwnerFindModalOk = async () => { + logImEXEvent("job_import_new"); + + setOwnerModalVisible(false); + setInsertLoading(true); + + if ( + !( + estData.data && + estData.data.available_jobs_by_pk && + estData.data.available_jobs_by_pk.est_data + ) + ) { + //We don't have the right data. Error! + setInsertLoading(false); + notification["error"]({ + message: t("jobs.errors.creating", { error: "No job data present." }), + }); + return; + } + + const newTotals = ( + await Axios.post("/job/totals", { + job: { + ...estData.data.available_jobs_by_pk.est_data, + joblines: estData.data.available_jobs_by_pk.est_data.joblines.data, + }, + }) + ).data; + + let existingVehicles; + if (estData.data.available_jobs_by_pk.est_data.vehicle) { + //There's vehicle data, need to double check the VIN. + existingVehicles = await client.query({ + query: SEARCH_VEHICLE_BY_VIN, + variables: { + vin: estData.data.available_jobs_by_pk.est_data.vehicle.data.v_vin, + }, + }); + } + + const newJob = { + ...estData.data.available_jobs_by_pk.est_data, + clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), + owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"), + job_totals: newTotals, + queued_for_parts: true, + ...(existingVehicles && existingVehicles.data.vehicles.length > 0 + ? { vehicleid: existingVehicles.data.vehicles[0].id, vehicle: null } + : {}), + }; + + insertNewJob({ + variables: { + job: selectedOwner + ? Object.assign( + {}, + newJob, + { owner: null }, + { ownerid: selectedOwner } + ) + : newJob, + }, + }) + .then((r) => { + 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. + + deleteJob({ + variables: { id: estData.data.available_jobs_by_pk.id }, + }).then((r) => { + refetch(); + setInsertLoading(false); + }); + }) + .catch((r) => { + //error while inserting + notification["error"]({ + message: t("jobs.errors.creating", { error: r.message }), + }); + refetch(); + setInsertLoading(false); + }); + }; + + const onJobFindModalOk = async () => { + logImEXEvent("job_import_supplement"); + + setJobModalVisible(false); + setInsertLoading(true); + + if ( + !( + estData.data && + estData.data.available_jobs_by_pk && + estData.data.available_jobs_by_pk.est_data + ) + ) { + //We don't have the right data. Error! + setInsertLoading(false); + notification["error"]({ + message: t("jobs.errors.creating", { error: "No job data present." }), + }); + } else { + //create upsert job + let supp = _.cloneDeep(estData.data.available_jobs_by_pk.est_data); + + delete supp.owner; + delete supp.vehicle; + if (importOptions.overrideHeaders) { + HeaderFields.forEach((item) => delete supp[item]); + } + + const newTotals = ( + await Axios.post("/job/totals", { + job: { + ...estData.data.available_jobs_by_pk.est_data, + joblines: estData.data.available_jobs_by_pk.est_data.joblines.data, + }, + }) + ).data; + + let suppDelta = await GetSupplementDelta( + client, + selectedJob, + estData.data.available_jobs_by_pk.est_data.joblines.data + ); + + delete supp.joblines; + await client.mutate({ + mutation: gql` + ${suppDelta} + `, + }); + updateJob({ + variables: { + jobId: selectedJob, + job: { + ...supp, + clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), + owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat( + "0.00" + ), + job_totals: newTotals, + queued_for_parts: true, + }, + }, + }) + .then((r) => { + notification["success"]({ + message: t("jobs.successes.supplemented"), + onClick: () => { + history.push( + `/manage/jobs/${r.data.update_jobs.returning[0].id}` + ); + }, + }); + //Job has been inserted. Clean up the available jobs record. + deleteJob({ + variables: { id: estData.data.available_jobs_by_pk.id }, + }).then((r) => { + refetch(); + setInsertLoading(false); + }); + }) + .catch((r) => { + //error while inserting + notification["error"]({ + message: t("jobs.errors.creating", { error: r.message }), + }); + refetch(); + setInsertLoading(false); + }); + } + }; + + const owner = + estData.data && + estData.data.available_jobs_by_pk && + estData.data.available_jobs_by_pk.est_data && + estData.data.available_jobs_by_pk.est_data.owner && + estData.data.available_jobs_by_pk.est_data.owner.data && + !estData.data.available_jobs_by_pk.issupplement + ? estData.data.available_jobs_by_pk.est_data.owner.data + : null; + + const onOwnerModalCancel = () => { + setOwnerModalVisible(false); + setSelectedOwner(null); + }; + + const onJobModalCancel = () => { + setJobModalVisible(false); + modalSearchState[1](""); + setSelectedJob(null); + }; + + const addJobAsNew = (record) => { + loadEstData({ variables: { id: record.id } }); + setOwnerModalVisible(true); + }; + + const addJobAsSupp = (record) => { + loadEstData({ variables: { id: record.id } }); + modalSearchState[1](record.clm_no); + setJobModalVisible(true); + }; + + if (error) return ; + return ( + + + + + + ); +} +export default connect(mapStateToProps, null)(JobsAvailableContainer); diff --git a/client/src/graphql/available-jobs.queries.js b/client/src/graphql/available-jobs.queries.js index 7268410fb..4ee8b47e0 100644 --- a/client/src/graphql/available-jobs.queries.js +++ b/client/src/graphql/available-jobs.queries.js @@ -1,5 +1,29 @@ import gql from "graphql-tag"; +export const QUERY_AVAILABLE_JOBS = gql` + query QUERY_AVAILABLE_JOBS { + available_jobs(order_by: { updated_at: desc }) { + cieca_id + clm_amt + clm_no + created_at + id + issupplement + ownr_name + source_system + supplement_number + updated_at + uploaded_by + ins_co_nm + vehicle_info + job { + id + ro_number + } + } + } +`; + export const QUERY_AVAILABLE_NEW_JOBS = gql` query QUERY_AVAILABLE_NEW_JOBS { available_jobs( @@ -58,6 +82,14 @@ export const DELETE_AVAILABLE_JOB = gql` } `; +export const DELETE_ALL_AVAILABLE_JOBS = gql` + mutation DELETE_ALL_AVAILABLE_JOBS { + delete_available_jobs(where: {}) { + affected_rows + } + } +`; + export const DELETE_ALL_AVAILABLE_NEW_JOBS = gql` mutation DELETE_ALL_AVAILABLE_NEW_JOBS { delete_available_jobs(where: { issupplement: { _eq: false } }) { diff --git a/client/src/pages/jobs-available/jobs-available.page.component.jsx b/client/src/pages/jobs-available/jobs-available.page.component.jsx deleted file mode 100644 index e92db9064..000000000 --- a/client/src/pages/jobs-available/jobs-available.page.component.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from "react"; -import { Link } from "react-router-dom"; -import { Button } from "antd"; -import JobsAvailableContainer from "../../components/jobs-available-new/jobs-available-new.container"; -import JobsAvailableSupplementContainer from "../../components/jobs-available-supplement/jobs-available-supplement.container"; -import { useTranslation } from "react-i18next"; -export default function JobsAvailablePageComponent({ - deleteJob, - estDataLazyLoad -}) { - const { t } = useTranslation(); - return ( -
- - - - - - - -
- ); -} diff --git a/client/src/pages/jobs-available/jobs-available.page.container.jsx b/client/src/pages/jobs-available/jobs-available.page.container.jsx index 91ceef6c8..afc12ad72 100644 --- a/client/src/pages/jobs-available/jobs-available.page.container.jsx +++ b/client/src/pages/jobs-available/jobs-available.page.container.jsx @@ -1,18 +1,15 @@ +import { Button } from "antd"; import React, { useEffect } from "react"; -import { useMutation, useLazyQuery } from "@apollo/react-hooks"; -import { - DELETE_AVAILABLE_JOB, - QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK, -} from "../../graphql/available-jobs.queries"; -import JobsAvailablePageComponent from "./jobs-available.page.component"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import JobsAvailableTableContainer from "../../components/jobs-available-table/jobs-available-table.container"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; - import { setBreadcrumbs, setSelectedHeader, } from "../../redux/application/application.actions"; + const mapDispatchToProps = (dispatch) => ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), @@ -22,11 +19,8 @@ export function JobsAvailablePageContainer({ setBreadcrumbs, setSelectedHeader, }) { - const [deleteJob] = useMutation(DELETE_AVAILABLE_JOB); const { t } = useTranslation(); - const estDataLazyLoad = useLazyQuery(QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK); - useEffect(() => { document.title = t("titles.jobsavailable"); setSelectedHeader("availablejobs"); @@ -38,10 +32,10 @@ export function JobsAvailablePageContainer({ return (
- + + + +
); diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index b0ef7aeec..115522111 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -1056,8 +1056,7 @@ "associationwarning": "Any changes to associations will require updating the data from the new parent record to the job.", "audit": "Audit Trail", "available": "Available", - "availablenew": "Available New Jobs", - "availablesupplements": "Available Supplements", + "availablejobs": "Available Jobs", "cards": { "customer": "Customer Information", "damage": "Area of Damage", diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 2d441da48..1c6ef45df 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -1056,8 +1056,7 @@ "associationwarning": "", "audit": "", "available": "", - "availablenew": "", - "availablesupplements": "", + "availablejobs": "", "cards": { "customer": "Información al cliente", "damage": "Área de Daño", diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index fb9284a2d..245d3a340 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -1056,8 +1056,7 @@ "associationwarning": "", "audit": "", "available": "", - "availablenew": "", - "availablesupplements": "", + "availablejobs": "", "cards": { "customer": "Informations client", "damage": "Zone de dommages",