diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index 120ba6e46..0ef9e24dd 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -4291,6 +4291,199 @@ + + checklist + + + actions + + + printall + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + errors + + + complete + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + nochecklist + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + labels + + + addtoproduction + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + checklist + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + printpack + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + removefromproduction + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + successes + + + completed + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + + + contracts @@ -8862,178 +9055,6 @@ - - intake - - - actions - - - printall - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - - - - - errors - - - intake - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - - - nochecklist - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - - - - - labels - - - addtoproduction - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - - - checklist - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - - - printpack - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - - - - - successes - - - intake - false - - - - - - en-US - false - - - es-MX - false - - - fr-CA - false - - - - - - - invoicelines @@ -11298,6 +11319,27 @@ + + deliver + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + export false @@ -22682,6 +22724,27 @@ + + jobs-deliver + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + jobs-detail false @@ -23272,6 +23335,27 @@ + + jobs-deliver + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + jobs-intake false diff --git a/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx b/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx new file mode 100644 index 000000000..5b668cccc --- /dev/null +++ b/client/src/components/job-checklist/components/job-checklist-form/job-checklist-form.component.jsx @@ -0,0 +1,162 @@ +import { useMutation } from "@apollo/react-hooks"; +import { Button, Form, notification, Switch } from "antd"; +import queryString from "query-string"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { connect } from "react-redux"; +import { useHistory, useLocation, useParams } from "react-router-dom"; +import { createStructuredSelector } from "reselect"; +import { MARK_LATEST_APPOINTMENT_AS_ARRIVED } from "../../../../graphql/appointments.queries"; +import { UPDATE_JOB } from "../../../../graphql/jobs.queries"; +import { selectBodyshop } from "../../../../redux/user/user.selectors"; +import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component"; +import ConfigFormComponents from "../../../config-form-components/config-form-components.component"; +import { logImEXEvent } from "../../../../firebase/firebase.utils"; + +const mapStateToProps = createStructuredSelector({ + bodyshop: selectBodyshop, +}); +const mapDispatchToProps = (dispatch) => ({ + //setUserLanguage: language => dispatch(setUserLanguage(language)) +}); + +export function JobChecklistForm({ formItems, bodyshop, type }) { + const { t } = useTranslation(); + const [intakeJob] = useMutation(UPDATE_JOB); + const [loading, setLoading] = useState(false); + const [markAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_AS_ARRIVED); + const { jobId } = useParams(); + const history = useHistory(); + const search = queryString.parse(useLocation().search); + + const handleFinish = async (values) => { + setLoading(true); + logImEXEvent("job_complete_intake"); + + const result = await intakeJob({ + variables: { + jobId: jobId, + job: { + ...(type === "intake" && { inproduction: values.addToProduction }), + status: + (type === "intake" && bodyshop.md_ro_statuses.default_arrived) || + (type === "deliver" && bodyshop.md_ro_statuses.default_delivered), + ...(type === "intake" && { actual_in: new Date() }), + + ...(type === "intake" && { + scheduled_completion: values.scheduledCompletion, + }), + + [(type === "intake" && "intakechecklist") || + (type === "deliver" && "deliverchecklist")]: values, + + ...(type === "deliver" && { + scheduled_delivery: values.scheduledDelivery, + }), + ...(type === "deliver" && + values.removeFromProduction && { + inproduction: false, + }), + }, + }, + }); + if (!!search.appointmentId) { + const appUpdate = await markAptArrived({ + variables: { appointmentId: search.appointmentId }, + }); + + if (!!appUpdate.errors) { + notification["error"]({ + message: t("checklist.errors.complete", { + error: JSON.stringify(result.errors), + }), + }); + } + } + setLoading(false); + + if (!!!result.errors) { + notification["success"]({ message: t("checklist.successes.completed") }); + history.push(`/manage/jobs/${jobId}`); + } else { + notification["error"]({ + message: t("checklist.errors.complete", { + error: JSON.stringify(result.errors), + }), + }); + } + }; + + const [form] = Form.useForm(); + + return ( +
+ {t("checklist.labels.checklist")} + + + + {type === "intake" && ( +
+ + + + + + + + + +
+ )} + {type === "deliver" && ( +
+ + + + + + +
+ )} + + + + ); +} + +export default connect(mapStateToProps, mapDispatchToProps)(JobChecklistForm); diff --git a/client/src/components/job-intake/components/job-intake-template-item/job-intake-template-item.component.jsx b/client/src/components/job-checklist/components/job-checklist-template-item/job-checklist-template-item.component.jsx similarity index 69% rename from client/src/components/job-intake/components/job-intake-template-item/job-intake-template-item.component.jsx rename to client/src/components/job-checklist/components/job-checklist-template-item/job-checklist-template-item.component.jsx index d5069c7ec..01590e8dc 100644 --- a/client/src/components/job-intake/components/job-intake-template-item/job-intake-template-item.component.jsx +++ b/client/src/components/job-checklist/components/job-checklist-template-item/job-checklist-template-item.component.jsx @@ -1,6 +1,9 @@ import React from "react"; import { PrinterFilled } from "@ant-design/icons"; -export default function JobIntakeTemplateItem({ templateKey, renderTemplate }) { +export default function JobChecklistTemplateItem({ + templateKey, + renderTemplate, +}) { return (
{templateKey} diff --git a/client/src/components/job-intake/components/job-intake-template-list/job-intake-template-list.component.jsx b/client/src/components/job-checklist/components/job-checklist-template-list/job-checklist-template-list.component.jsx similarity index 84% rename from client/src/components/job-intake/components/job-intake-template-list/job-intake-template-list.component.jsx rename to client/src/components/job-checklist/components/job-checklist-template-list/job-checklist-template-list.component.jsx index 722367e89..994504b9e 100644 --- a/client/src/components/job-intake/components/job-intake-template-list/job-intake-template-list.component.jsx +++ b/client/src/components/job-checklist/components/job-checklist-template-list/job-checklist-template-list.component.jsx @@ -1,5 +1,5 @@ import React from "react"; -import JobIntakeTemplateItem from "../job-intake-template-item/job-intake-template-item.component"; +import JobIntakeTemplateItem from "../job-checklist-template-item/job-checklist-template-item.component"; import { useParams } from "react-router-dom"; import RenderTemplate, { displayTemplateInWindow, @@ -22,9 +22,8 @@ const mapDispatchToProps = (dispatch) => ({ export function JobIntakeTemplateList({ bodyshop, templates }) { const { jobId } = useParams(); const { t } = useTranslation(); - //TODO SHould this be using the generic one? const renderTemplate = async (templateKey) => { - logImEXEvent("job_intake_template_render"); + logImEXEvent("job_checklist_template_render"); const html = await RenderTemplate( { @@ -37,7 +36,7 @@ export function JobIntakeTemplateList({ bodyshop, templates }) { }; const renderAllTemplates = () => { - logImEXEvent("job_intake_render_all_templates"); + logImEXEvent("job_checklist_render_all_templates"); templates.forEach((template) => renderTemplate(template)); }; @@ -46,7 +45,7 @@ export function JobIntakeTemplateList({ bodyshop, templates }) {
{t("intake.labels.printpack")} {templates.map((template) => ( + + + + + + + + ); +} diff --git a/client/src/components/job-intake/components/job-intake-form/job-intake-form.component.jsx b/client/src/components/job-intake/components/job-intake-form/job-intake-form.component.jsx deleted file mode 100644 index 07a3b52f1..000000000 --- a/client/src/components/job-intake/components/job-intake-form/job-intake-form.component.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { useMutation } from "@apollo/react-hooks"; -import { Button, Form, notification, Switch } from "antd"; -import queryString from "query-string"; -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { connect } from "react-redux"; -import { useHistory, useLocation, useParams } from "react-router-dom"; -import { createStructuredSelector } from "reselect"; -import { MARK_LATEST_APPOINTMENT_AS_ARRIVED } from "../../../../graphql/appointments.queries"; -import { UPDATE_JOB } from "../../../../graphql/jobs.queries"; -import { selectBodyshop } from "../../../../redux/user/user.selectors"; -import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component"; -import ConfigFormComponents from "../../../config-form-components/config-form-components.component"; -import { logImEXEvent } from "../../../../firebase/firebase.utils"; - -const mapStateToProps = createStructuredSelector({ - bodyshop: selectBodyshop, -}); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) -}); - -export function JobIntakeForm({ formItems, bodyshop }) { - const { t } = useTranslation(); - const [intakeJob] = useMutation(UPDATE_JOB); - const [loading, setLoading] = useState(false); - const [markAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_AS_ARRIVED); - const { jobId } = useParams(); - const history = useHistory(); - const search = queryString.parse(useLocation().search); - - const handleFinish = async (values) => { - setLoading(true); - logImEXEvent("job_complete_intake"); - - const result = await intakeJob({ - variables: { - jobId: jobId, - job: { - inproduction: values.addToProduction || false, - status: bodyshop.md_ro_statuses.default_arrived || "Arrived*", - actual_in: new Date(), - scheduled_completion: values.scheduledCompletion, - intakechecklist: values, - scheduled_delivery: values.scheduledDelivery, - }, - }, - }); - if (!!search.appointmentId) { - const appUpdate = await markAptArrived({ - variables: { appointmentId: search.appointmentId }, - }); - - if (!!appUpdate.errors) { - notification["error"]({ - message: t("intake.errors.intake", { - error: JSON.stringify(result.errors), - }), - }); - } - } - - if (!!!result.errors) { - notification["success"]({ message: t("intake.successes.intake") }); - history.push(`/manage/jobs/${jobId}`); - } else { - notification["error"]({ - message: t("intake.errors.intake", { - error: JSON.stringify(result.errors), - }), - }); - } - setLoading(false); - }; - - const [form] = Form.useForm(); - - return ( -
- {t("intake.labels.checklist")} - - - - - - - - - - - - - - - ); -} - -export default connect(mapStateToProps, mapDispatchToProps)(JobIntakeForm); diff --git a/client/src/components/job-intake/job-intake.component.jsx b/client/src/components/job-intake/job-intake.component.jsx deleted file mode 100644 index 79108fffe..000000000 --- a/client/src/components/job-intake/job-intake.component.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from "react"; -import JobIntakeTemplateList from "./components/job-intake-template-list/job-intake-template-list.component"; -import JobIntakeForm from "./components/job-intake-form/job-intake-form.component"; -import { Row, Col } from "antd"; -export default function JobIntakeComponent({ intakeChecklistConfig }) { - const { form, templates } = intakeChecklistConfig; - - return ( - - - - - - - - - ); -} diff --git a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx index b86523e89..89b65b33f 100644 --- a/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx +++ b/client/src/components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx @@ -66,6 +66,15 @@ export function JobsDetailHeaderActions({ )} + + {!!job.deliveryhecklist ? ( + t("jobs.actions.deliver") + ) : ( + + {t("jobs.actions.deliver")} + + )} + { diff --git a/client/src/components/shop-info/shop-info.intake.component.jsx b/client/src/components/shop-info/shop-info.intake.component.jsx index 2256ca56f..3a555f832 100644 --- a/client/src/components/shop-info/shop-info.intake.component.jsx +++ b/client/src/components/shop-info/shop-info.intake.component.jsx @@ -118,7 +118,129 @@ export default function ShopInfoIntakeChecklistComponent({ form }) { + + + + + + {(fields, { add, remove, move }) => { + return ( +
+ {fields.map((field, index) => ( + +
+ + + + + + + + + + + + + + + + + { + remove(field.name); + }} + /> + +
+
+ ))} + + + +
+ ); + }} +
+ + + + + + ({ + setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), +}); + +export function JobsDeliverContainer({ bodyshop, setBreadcrumbs }) { + const { t } = useTranslation(); + const { jobId } = useParams(); + const { loading, error, data } = useQuery(QUERY_DELIVER_CHECKLIST, { + variables: { shopId: bodyshop.id, jobId: jobId }, + }); + + useEffect(() => { + document.title = t("titles.jobs-deliver"); + setBreadcrumbs([ + { link: "/manage/jobs", label: t("titles.bc.jobs") }, + { + link: `/manage/jobs/${jobId}`, + label: t("titles.bc.jobs-detail", { + number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || "", + }), + }, + { + link: `/manage/jobs/${jobId}/deliver`, + label: t("titles.bc.jobs-deliver"), + }, + ]); + }, [t, setBreadcrumbs, jobId, data]); + + if (loading) return ; + if (error) return ; + if (data && !!!data.bodyshops_by_pk.deliverchecklist) + return ( + + ); + return ( + +
+ +
+
+ ); +} + +export default connect( + mapStateToProps, + mapDispatchToProps +)(JobsDeliverContainer); diff --git a/client/src/pages/jobs-intake/jobs-intake.page.container.jsx b/client/src/pages/jobs-intake/jobs-intake.page.container.jsx index c55ceb82f..335ce8930 100644 --- a/client/src/pages/jobs-intake/jobs-intake.page.container.jsx +++ b/client/src/pages/jobs-intake/jobs-intake.page.container.jsx @@ -5,7 +5,7 @@ import { connect } from "react-redux"; import { useParams } from "react-router-dom"; import { createStructuredSelector } from "reselect"; import AlertComponent from "../../components/alert/alert.component"; -import JobIntakeComponent from "../../components/job-intake/job-intake.component"; +import JobChecklist from "../../components/job-checklist/job-checklist.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import { QUERY_INTAKE_CHECKLIST } from "../../graphql/bodyshop.queries"; import { setBreadcrumbs } from "../../redux/application/application.actions"; @@ -22,11 +22,11 @@ const mapDispatchToProps = (dispatch) => ({ export function JobsIntakeContainer({ bodyshop, setBreadcrumbs }) { const { t } = useTranslation(); + const { jobId } = useParams(); const { loading, error, data } = useQuery(QUERY_INTAKE_CHECKLIST, { - variables: { shopId: bodyshop.id }, + variables: { shopId: bodyshop.id, jobId: jobId }, }); - const { jobId } = useParams(); useEffect(() => { document.title = t("titles.jobs-intake"); @@ -34,14 +34,18 @@ export function JobsIntakeContainer({ bodyshop, setBreadcrumbs }) { { link: "/manage/jobs", label: t("titles.bc.jobs") }, { link: `/manage/jobs/${jobId}`, - label: t("titles.bc.jobs-detail", { number: "TODO" }), + label: t("titles.bc.jobs-detail", { + number: + (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || + data.jobs_by_pk.est_number, + }), }, { link: `/manage/jobs/${jobId}/intake`, label: t("titles.bc.jobs-intake"), }, ]); - }, [t, setBreadcrumbs, jobId]); + }, [t, setBreadcrumbs, jobId, data]); if (loading) return ; if (error) return ; @@ -52,10 +56,9 @@ export function JobsIntakeContainer({ bodyshop, setBreadcrumbs }) { return (
-
diff --git a/client/src/pages/manage/manage.page.component.jsx b/client/src/pages/manage/manage.page.component.jsx index c95124b91..9025fc21a 100644 --- a/client/src/pages/manage/manage.page.component.jsx +++ b/client/src/pages/manage/manage.page.component.jsx @@ -102,6 +102,9 @@ const ShopTemplates = lazy(() => const JobIntake = lazy(() => import("../jobs-intake/jobs-intake.page.container") ); +const JobDeliver = lazy(() => + import("../jobs-deliver/jobs-delivery.page.container") +); const AccountingReceivables = lazy(() => import("../accounting-receivables/accounting-receivables.container") ); @@ -190,6 +193,11 @@ export function Manage({ match, conflict }) { path={`${match.path}/jobs/:jobId/intake`} component={JobIntake} /> +