generalized job checklist functionality & created deliver checklist. BOD-376

This commit is contained in:
Patrick Fic
2020-09-02 15:32:29 -07:00
parent ed788a4b5c
commit 51b6a560a6
39 changed files with 2646 additions and 378 deletions

View File

@@ -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 (
<Form
form={form}
onFinish={handleFinish}
initialValues={{ ...(type === "intake" && { addToProduction: true }) }}
>
{t("checklist.labels.checklist")}
<ConfigFormComponents componentList={formItems} />
{type === "intake" && (
<div>
<Form.Item
name="addToProduction"
valuePropName="checked"
label={t("checklist.labels.addtoproduction")}
>
<Switch />
</Form.Item>
<Form.Item
name="scheduledCompletion"
label={t("jobs.fields.scheduled_completion")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<DateTimePicker />
</Form.Item>
<Form.Item
name="scheduledDelivery"
label={t("jobs.fields.scheduled_delivery")}
>
<DateTimePicker />
</Form.Item>
</div>
)}
{type === "deliver" && (
<div>
<Form.Item
name="actualCompletion"
label={t("jobs.fields.actual_completion")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<DateTimePicker />
</Form.Item>
<Form.Item
name="removeFromProduction"
valuePropName="checked"
label={t("checklist.labels.removefromproduction")}
>
<Switch />
</Form.Item>
</div>
)}
<Button loading={loading} htmlType="submit">
{t("general.actions.submit")}
</Button>
</Form>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobChecklistForm);

View File

@@ -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 (
<div>
{templateKey}

View File

@@ -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 }) {
<div>
{t("intake.labels.printpack")}
<Button onClick={renderAllTemplates}>
{t("intake.actions.printall")}
{t("checklist.actions.printall")}
</Button>
{templates.map((template) => (
<JobIntakeTemplateItem

View File

@@ -0,0 +1,18 @@
import React from "react";
import JobChecklistTemplateList from "./components/job-checklist-template-list/job-checklist-template-list.component";
import JobChecklistForm from "./components/job-checklist-form/job-checklist-form.component";
import { Row, Col } from "antd";
export default function JobIntakeComponent({ checklistConfig, type }) {
const { form, templates } = checklistConfig;
return (
<Row>
<Col span={12}>
<JobChecklistTemplateList templates={templates} type={type} />
</Col>
<Col span={12}>
<JobChecklistForm formItems={form} type={type} />
</Col>
</Row>
);
}

View File

@@ -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 (
<Form
form={form}
onFinish={handleFinish}
initialValues={{ addToProduction: true }}
>
{t("intake.labels.checklist")}
<ConfigFormComponents componentList={formItems} />
<Form.Item
name="addToProduction"
valuePropName="checked"
label={t("intake.labels.addtoproduction")}
>
<Switch />
</Form.Item>
<Form.Item
name="scheduledCompletion"
label={t("jobs.fields.scheduled_completion")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<DateTimePicker />
</Form.Item>
<Form.Item
name="scheduledDelivery"
label={t("jobs.fields.scheduled_delivery")}
>
<DateTimePicker />
</Form.Item>
<Button loading={loading} htmlType="submit">
{t("general.actions.submit")}
</Button>
</Form>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobIntakeForm);

View File

@@ -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 (
<Row>
<Col span={12}>
<JobIntakeTemplateList templates={templates} />
</Col>
<Col span={12}>
<JobIntakeForm formItems={form} />
</Col>
</Row>
);
}

View File

@@ -66,6 +66,15 @@ export function JobsDetailHeaderActions({
</Link>
)}
</Menu.Item>
<Menu.Item disabled={!!job.deliverchecklist}>
{!!job.deliveryhecklist ? (
t("jobs.actions.deliver")
) : (
<Link to={`/manage/jobs/${job.id}/deliver`}>
{t("jobs.actions.deliver")}
</Link>
)}
</Menu.Item>
<Menu.Item
key="enterpayments"
onClick={() => {

View File

@@ -118,7 +118,129 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
<SelectorDiv>
<Form.Item
name={["intakechecklist", "templates"]}
label={t("bodyshop.fields.statuses.active_statuses")}
label={t("bodyshop.fields.intake.templates")}
rules={[
{
required: true,
message: t("general.validation.required"),
type: "array",
},
]}
>
<Select mode="multiple">
{Object.keys(TemplateList).map((i) => (
<Select.Option
key={TemplateList[i].key}
value={TemplateList[i].key}
>
{TemplateList[i].title}
</Select.Option>
))}
</Select>
</Form.Item>
</SelectorDiv>
<Form.List name={["deliverchecklist", "form"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
<div className="imex-flex-row">
<Form.Item
className="imex-flex-row__margin"
label={t("jobs.fields.intake.name")}
key={`${index}named`}
name={[field.name, "name"]}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
className="imex-flex-row__margin"
label={t("jobs.fields.intake.type")}
key={`${index}typed`}
name={[field.name, "type"]}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<Select>
{Object.keys(ConfigFormTypes).map((i, idx) => (
<Select.Option key={i} value={i}>
{i}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
className="imex-flex-row__margin"
label={t("jobs.fields.intake.label")}
key={`${index}labeld`}
name={[field.name, "label"]}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
className="imex-flex-row__margin"
label={t("jobs.fields.intake.required")}
key={`${index}requiredd`}
name={[field.name, "required"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</div>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<SelectorDiv>
<Form.Item
name={["deliverchecklist", "templates"]}
label={t("bodyshop.fields.deliver.templates")}
rules={[
{
required: true,

View File

@@ -213,6 +213,18 @@ export default function ShopInfoRbacComponent({ form }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.deliver")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:deliver"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.detail")}
rules={[