diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 274cca523..49e212e03 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -4088,6 +4088,27 @@ + + manualnew + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + postInvoices false @@ -6350,27 +6371,6 @@ - - available_new_jobs - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - availablenew false @@ -6694,6 +6694,27 @@ + + newowner + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + newvehicle false @@ -7968,6 +7989,27 @@ + + selectexistingornew + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + @@ -9024,6 +9066,27 @@ + + selectexistingornew + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + validation false diff --git a/client/src/components/dashboard-grid/dashboard-grid.component.jsx b/client/src/components/dashboard-grid/dashboard-grid.component.jsx index 441f38302..787842fc8 100644 --- a/client/src/components/dashboard-grid/dashboard-grid.component.jsx +++ b/client/src/components/dashboard-grid/dashboard-grid.component.jsx @@ -13,7 +13,7 @@ const Sdiv = styled.div` width: 80%; top: 10%; left: 10%; - background-color: #ffcc00; + // background-color: #ffcc00; `; const ResponsiveReactGridLayout = WidthProvider(Responsive); diff --git a/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx b/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx new file mode 100644 index 000000000..770e68c3e --- /dev/null +++ b/client/src/components/jobs-create-jobs-info/jobs-create-jobs-info.component.jsx @@ -0,0 +1,290 @@ +import { Collapse, Form, Input, InputNumber, Switch, DatePicker } from "antd"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import FormItemEmail from "../form-items-formatted/email-form-item.component"; +import FormItemPhone from "../form-items-formatted/phone-form-item.component"; + +export default function JobsCreateJobsInfo({ form }) { + const { t } = useTranslation(); + const { getFieldValue } = form; + return ( +
+ + + + + + + + + + + + + + + TODO: missing KOL field??? + + + + CAA # seems not correct based on field mapping Class seems not correct + based on field mapping + + + + + + + + + + + + + + + + + + + + + + Appraiser Info + + + + + + + + + + TODO: Field is pay date but title is inspection date. Likely + incorrect? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TODO How to handle different taxes and marking them as exempt? + { + // + // {getFieldDecorator("exempt", { + // initialValue: job.exempt + // })()} + // + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TODO This is equivalent of GST payable. + + + + TODO equivalent of other customer amount + + + + + + + + + + + + + Totals Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + Note //TODO Remove ATP rate? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.component.jsx b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.component.jsx index 22d03304a..b3de7c85a 100644 --- a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.component.jsx +++ b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.component.jsx @@ -1,15 +1,27 @@ import React from "react"; import { Row, Col, Typography } from "antd"; +import JobsCreateOwnerInfoNewComponent from "./jobs-create-owner-info.new.component"; +import JobsCreateOwnerInfoSearchComponent from "./jobs-create-owner-info.search.component"; +import { useTranslation } from "react-i18next"; + export default function JobsCreateOwnerInfoComponent({ loading, owners }) { + const { t } = useTranslation(); return (
- XX + {t("jobs.labels.create.ownerinfo")} - - + + + + - + + +
); diff --git a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.container.jsx b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.container.jsx index 1c5fa75ff..2abe03e28 100644 --- a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.container.jsx +++ b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.container.jsx @@ -9,14 +9,14 @@ export default function JobsCreateOwnerContainer() { const [state] = useContext(JobCreateContext); const { loading, error, data } = useQuery(QUERY_SEARCH_OWNER_BY_IDX, { variables: { search: `%${state.owner.search}%` }, - skip: !state.vehicle.search + skip: !state.owner.search }); - if (error) return ; + if (error) return ; return ( ); } diff --git a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx new file mode 100644 index 000000000..2b3fa0380 --- /dev/null +++ b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.new.component.jsx @@ -0,0 +1,122 @@ +import { Form, Input, Checkbox, Switch } from "antd"; +import React, { useContext } from "react"; +import { useTranslation } from "react-i18next"; +import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; +import FormItemEmail from "../form-items-formatted/email-form-item.component"; +import FormItemPhone from "../form-items-formatted/phone-form-item.component"; + +export default function JobsCreateOwnerInfoNewComponent() { + const [state, setState] = useContext(JobCreateContext); + + const { t } = useTranslation(); + return ( +
+ { + setState({ + ...state, + owner: { + ...state.owner, + new: !state.owner.new, + selectedid: null + } + }); + }} + > + {t("jobs.labels.create.newowner")} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx index 7ef0d19bb..3ecfb798d 100644 --- a/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx +++ b/client/src/components/jobs-create-owner-info/jobs-create-owner-info.search.component.jsx @@ -1,9 +1,9 @@ +import { Input, Table } from "antd"; import React, { useContext, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Table, Input } from "antd"; -import { Link } from "react-router-dom"; -import { alphaSort } from "../../utils/sorters"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; +import PhoneFormatter from "../../utils/PhoneFormatter"; +import { alphaSort } from "../../utils/sorters"; export default function JobsCreateOwnerInfoSearchComponent({ loading, @@ -19,34 +19,61 @@ export default function JobsCreateOwnerInfoSearchComponent({ const columns = [ { - title: t("vehicles.fields.v_vin"), - dataIndex: "v_vin", - key: "v_vin", - sorter: (a, b) => alphaSort(a.v_vin, b.v_vin), + title: t("owners.fields.ownr_ln"), + dataIndex: "ownr_ln", + key: "ownr_ln", + sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), sortOrder: - tableState.sortedInfo.columnKey === "v_vin" && - tableState.sortedInfo.order, + tableState.sortedInfo.columnKey === "ownr_ln" && + tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_fn"), + dataIndex: "ownr_fn", + key: "ownr_fn", + sorter: (a, b) => alphaSort(a.ownr_fn, b.ownr_fn), + sortOrder: + tableState.sortedInfo.columnKey === "ownr_fn" && + tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_addr1"), + dataIndex: "ownr_addr1", + key: "ownr_addr1", + sorter: (a, b) => alphaSort(a.ownr_addr1, b.ownr_addr1), + sortOrder: + tableState.sortedInfo.columnKey === "ownr_addr1" && + tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_city"), + dataIndex: "ownr_city", + key: "ownr_city", + sorter: (a, b) => alphaSort(a.ownr_city, b.ownr_city), + sortOrder: + tableState.sortedInfo.columnKey === "ownr_city" && + tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_ea"), + dataIndex: "ownr_ea", + key: "ownr_ea", + sorter: (a, b) => alphaSort(a.ownr_ea, b.ownr_ea), + sortOrder: + tableState.sortedInfo.columnKey === "ownr_ea" && + tableState.sortedInfo.order + }, + { + title: t("owners.fields.ownr_ph1"), + dataIndex: "ownr_ph1", + key: "ownr_ph1", render: (text, record) => ( - {record.v_vin} - ) - }, - { - title: t("vehicles.fields.description"), - dataIndex: "description", - key: "description", - render: (text, record) => { - return ( - {`${record.v_model_yr} ${record.v_make_desc} ${record.v_model_desc} ${record.v_color}`} - ); - } - }, - { - title: t("vehicles.fields.plate_no"), - dataIndex: "plate", - key: "plate", - render: (text, record) => { - return {`${record.plate_st} | ${record.plate_no}`}; - } + {record.ownr_ph1} + ), + sorter: (a, b) => alphaSort(a.ownr_ph1, b.ownr_ph1), + sortOrder: + tableState.sortedInfo.columnKey === "ownr_ph1" && + tableState.sortedInfo.order } ]; @@ -61,32 +88,32 @@ export default function JobsCreateOwnerInfoSearchComponent({ title={() => { return ( { setState({ ...state, - vehicle: { ...state.vehicle, search: value } + owner: { ...state.owner, search: value } }); }} enterButton /> ); }} - size='small' + size="small" pagination={{ position: "top" }} columns={columns.map(item => ({ ...item }))} - rowKey='id' - dataSource={vehicles} + rowKey="id" + dataSource={owners} onChange={handleTableChange} rowSelection={{ onSelect: props => { setState({ ...state, - vehicle: { ...state.vehicle, new: false, selectedid: props.id } + owner: { ...state.owner, new: false, selectedid: props.id } }); }, type: "radio", - selectedRowKeys: [state.vehicle.selectedid] + selectedRowKeys: [state.owner.selectedid] }} onRow={(record, rowIndex) => { return { @@ -95,8 +122,8 @@ export default function JobsCreateOwnerInfoSearchComponent({ if (record.id) { setState({ ...state, - vehicle: { - ...state.vehicle, + owner: { + ...state.owner, new: false, selectedid: record.id } @@ -107,7 +134,7 @@ export default function JobsCreateOwnerInfoSearchComponent({ } setState({ ...state, - vehicle: { ...state.vehicle, selectedid: null } + owner: { ...state.owner, selectedid: null } }); } }; diff --git a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.component.jsx b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.component.jsx index 15e56eb48..39b3711a5 100644 --- a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.component.jsx +++ b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.component.jsx @@ -1,12 +1,10 @@ -import { Checkbox, Col, Row, Typography } from "antd"; -import React, { useContext } from "react"; -import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; +import { Col, Row, Typography } from "antd"; +import React from "react"; import { useTranslation } from "react-i18next"; import JobsCreateVehicleInfoNewComponent from "./jobs-create-vehicle-info.new.component"; import JobsCreateVehicleInfoSearchComponent from "./jobs-create-vehicle-info.search.component"; export default function JobsCreateVehicleInfoComponent({ loading, vehicles }) { - const [state, setState] = useContext(JobCreateContext); const { t } = useTranslation(); return (
@@ -19,22 +17,6 @@ export default function JobsCreateVehicleInfoComponent({ loading, vehicles }) { /> - { - setState({ - ...state, - vehicle: { - ...state.vehicle, - new: !state.vehicle.new, - selectedid: null - } - }); - }} - > - {t("jobs.labels.create.newvehicle")} - diff --git a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx index 17cf1727d..015c60e53 100644 --- a/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx +++ b/client/src/components/jobs-create-vehicle-info/jobs-create-vehicle-info.new.component.jsx @@ -1,14 +1,30 @@ -import { DatePicker, Form, Input } from "antd"; +import { DatePicker, Form, Input, Checkbox } from "antd"; import React, { useContext } from "react"; import { useTranslation } from "react-i18next"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; export default function JobsCreateVehicleInfoNewComponent() { - const [state] = useContext(JobCreateContext); + const [state, setState] = useContext(JobCreateContext); const { t } = useTranslation(); return (
+ { + setState({ + ...state, + vehicle: { + ...state.vehicle, + new: !state.vehicle.new, + selectedid: null + } + }); + }} + > + {t("jobs.labels.create.newvehicle")} + + + + + + content: , + validation: !!state.vehicle.new || !!state.vehicle.selectedid, + error: t("vehicles.errors.selectexistingornew") }, { title: t("jobs.labels.create.ownerinfo"), - content: + content: , + validation: !!state.owner.new || !!state.owner.selectedid, + error: t("owners.errors.selectexistingornew") }, { title: t("jobs.labels.create.jobinfo"), - content: "Last-content" + content: } ]; @@ -28,8 +38,49 @@ export default function JobsCreateComponent() { const prev = () => { setPageIndex(pageIndex - 1); }; - const { Step } = Steps; + + const ProgressButtons = () => { + return ( +
+ {pageIndex > 0 && ( + + )} + {pageIndex < steps.length - 1 && ( + + )} + {pageIndex === steps.length - 1 && ( + + )} +
+ ); + }; + return (
@@ -39,38 +90,33 @@ export default function JobsCreateComponent() { title={item.title} style={{ cursor: "pointer" }} onClick={() => { - setPageIndex(idx); + form + .validateFields() + .then(r => { + if (steps[pageIndex].validation) { + setErrorMessage(null); + setPageIndex(idx); + } else { + setErrorMessage(steps[pageIndex].error); + } + }) + .catch(error => console.log("error", error)); }} /> ))} + + + {errorMessage ? ( + + ) : null} {steps.map((item, idx) => (
{item.content}
))} - -
- {pageIndex > 0 && ( - - )} - {pageIndex < steps.length - 1 && ( - - )} - {pageIndex === steps.length - 1 && ( - - )} -
+
); } diff --git a/client/src/pages/jobs-create/jobs-create.container.jsx b/client/src/pages/jobs-create/jobs-create.container.jsx index b745431e7..e6ca9409e 100644 --- a/client/src/pages/jobs-create/jobs-create.container.jsx +++ b/client/src/pages/jobs-create/jobs-create.container.jsx @@ -2,28 +2,71 @@ import React, { useState } from "react"; import JobsCreateComponent from "./jobs-create.component"; import { Form } from "antd"; import JobCreateContext from "./jobs-create.context"; -export default function JobsCreateContainer() { - const [form] = Form.useForm(); +import { useMutation, useLazyQuery } from "react-apollo"; +import { INSERT_NEW_JOB } from "../../graphql/jobs.queries"; +import { QUERY_OWNER_FOR_JOB_CREATION } from "../../graphql/owners.queries"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { selectBodyshop } from "../../redux/user/user.selectors"; +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop +}); +export default connect(mapStateToProps, null)(JobsCreateContainer); +function JobsCreateContainer({ bodyshop }) { const contextState = useState({ vehicle: { new: false, search: "", selectedid: null }, owner: { new: false, search: "", selectedid: null }, job: null }); - + const [form] = Form.useForm(); const [state] = contextState; + const [insertJob] = useMutation(INSERT_NEW_JOB); + const [loadOwner] = useLazyQuery(QUERY_OWNER_FOR_JOB_CREATION); const handleFinish = values => { console.log("Form Values", values); - //const vehicleSpread = state.vehicle.selectedid ? {vehicleid: state.vehicle.selectedid} : {vehicle: {data: { ...values.vehicle} } - const job = Object.assign( + console.log("state", state); + let job = Object.assign( {}, + values, { vehicle: state.vehicle.selectedid ? null : values.vehicle, vehicleid: state.vehicle.selectedid || null + }, + { + owner: state.owner.selectedid ? null : values.owner, + ownerid: state.owner.selectedid || null + }, + { + status: bodyshop.md_ro_statuses.default_imported || "Open*", //Pull from redux store. + shopid: bodyshop.id } ); + + if (!!job.owner) { + //spread the owner into to the job + console.log("Spread New Owner"); + let ownerData = job.owner.data; + delete ownerData.allow_text_message; + delete ownerData.preferred_contact; + job = { ...job, ...ownerData }; + runInsertJob(job); + } else { + //lookup the owner and spread it then. + loadOwner({ + variables: { id: state.owner.selectedid }, + onCompleted: data => { + console.log("data", data); + runInsertJob({ ...job, ...data.owners_by_pk }); + } + }); + } + }; + + const runInsertJob = job => { console.log("Job To Save", job); + insertJob({ variables: { job: job } }); }; return ( diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 229cffc7f..384a32028 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -303,6 +303,7 @@ "addNote": "Add Note", "changestatus": "Change Status", "convert": "Convert", + "manualnew": "Create New Job Manually", "postInvoices": "Post Invoices", "printCenter": "Print Center", "schedule": "Schedule" @@ -416,7 +417,6 @@ "labels": { "appointmentconfirmation": "Send confirmation to customer?", "audit": "Audit Trail", - "available_new_jobs": "", "availablenew": "Available New Jobs", "availablesupplements": "Available Supplements", "cards": { @@ -435,6 +435,7 @@ }, "create": { "jobinfo": "Job Info", + "newowner": "Create a new Owner instead. ", "newvehicle": "Create a new Vehicle Instead", "ownerinfo": "Owner Info", "vehicleinfo": "Vehicle Info" @@ -522,7 +523,8 @@ }, "owners": { "errors": { - "noaccess": "The record does not exist or you do not have access to it. " + "noaccess": "The record does not exist or you do not have access to it. ", + "selectexistingornew": "Select an existing owner record or create a new one. " }, "fields": { "address": "Address", @@ -605,6 +607,7 @@ "vehicles": { "errors": { "noaccess": "The vehicle does not exist or you do not have access to it.", + "selectexistingornew": "Select an existing vehicle record or create a new one. ", "validation": "Please ensure all fields are entered correctly.", "validationtitle": "Validation Error" }, diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 1cfd7e801..20022889c 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -303,6 +303,7 @@ "addNote": "Añadir la nota", "changestatus": "Cambiar Estado", "convert": "Convertir", + "manualnew": "", "postInvoices": "Contabilizar facturas", "printCenter": "Centro de impresión", "schedule": "Programar" @@ -416,7 +417,6 @@ "labels": { "appointmentconfirmation": "¿Enviar confirmación al cliente?", "audit": "", - "available_new_jobs": "", "availablenew": "", "availablesupplements": "", "cards": { @@ -435,6 +435,7 @@ }, "create": { "jobinfo": "", + "newowner": "", "newvehicle": "", "ownerinfo": "", "vehicleinfo": "" @@ -522,7 +523,8 @@ }, "owners": { "errors": { - "noaccess": "El registro no existe o no tiene acceso a él." + "noaccess": "El registro no existe o no tiene acceso a él.", + "selectexistingornew": "" }, "fields": { "address": "Dirección", @@ -605,6 +607,7 @@ "vehicles": { "errors": { "noaccess": "El vehículo no existe o usted no tiene acceso a él.", + "selectexistingornew": "", "validation": "Asegúrese de que todos los campos se ingresen correctamente.", "validationtitle": "Error de validacion" }, diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 57a1c513d..cb31d9d0c 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -303,6 +303,7 @@ "addNote": "Ajouter une note", "changestatus": "Changer le statut", "convert": "Convertir", + "manualnew": "", "postInvoices": "Poster des factures", "printCenter": "Centre d'impression", "schedule": "Programme" @@ -416,7 +417,6 @@ "labels": { "appointmentconfirmation": "Envoyer une confirmation au client?", "audit": "", - "available_new_jobs": "", "availablenew": "", "availablesupplements": "", "cards": { @@ -435,6 +435,7 @@ }, "create": { "jobinfo": "", + "newowner": "", "newvehicle": "", "ownerinfo": "", "vehicleinfo": "" @@ -522,7 +523,8 @@ }, "owners": { "errors": { - "noaccess": "L'enregistrement n'existe pas ou vous n'y avez pas accès." + "noaccess": "L'enregistrement n'existe pas ou vous n'y avez pas accès.", + "selectexistingornew": "" }, "fields": { "address": "Adresse", @@ -605,6 +607,7 @@ "vehicles": { "errors": { "noaccess": "Le véhicule n'existe pas ou vous n'y avez pas accès.", + "selectexistingornew": "", "validation": "Veuillez vous assurer que tous les champs sont correctement entrés.", "validationtitle": "Erreur de validation" },