- Merge client update into test-beta

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-01-18 19:20:08 -05:00
696 changed files with 92291 additions and 107075 deletions

View File

@@ -1,337 +1,331 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, Input, notification, Switch } from "antd";
import moment from "moment-business-days";
import {useMutation} from "@apollo/client";
import {Button, Card, Form, Input, notification, Switch} from "antd";
import dayjs from "../../../../utils/day";
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 { logImEXEvent } from "../../../../firebase/firebase.utils";
import {
MARK_APPOINTMENT_ARRIVED,
MARK_LATEST_APPOINTMENT_ARRIVED,
} from "../../../../graphql/appointments.queries";
import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
import { insertAuditTrail } from "../../../../redux/application/application.actions";
import {
selectBodyshop,
selectCurrentUser,
} from "../../../../redux/user/user.selectors";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {useLocation, useNavigate, useParams} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import {logImEXEvent} from "../../../../firebase/firebase.utils";
import {MARK_APPOINTMENT_ARRIVED, MARK_LATEST_APPOINTMENT_ARRIVED,} from "../../../../graphql/appointments.queries";
import {UPDATE_JOB} from "../../../../graphql/jobs.queries";
import {UPDATE_OWNER} from "../../../../graphql/owners.queries";
import {insertAuditTrail} from "../../../../redux/application/application.actions";
import {selectBodyshop, selectCurrentUser,} from "../../../../redux/user/user.selectors";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
insertAuditTrail: ({jobid, operation}) =>
dispatch(insertAuditTrail({jobid, operation})),
});
export function JobChecklistForm({
insertAuditTrail,
formItems,
bodyshop,
currentUser,
type,
job,
readOnly = false,
}) {
const { t } = useTranslation();
const [intakeJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false);
const [markAptArrived] = useMutation(MARK_APPOINTMENT_ARRIVED);
const [markLatestAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_ARRIVED);
const [updateOwner] = useMutation(UPDATE_OWNER);
insertAuditTrail,
formItems,
bodyshop,
currentUser,
type,
job,
readOnly = false,
}) {
const {t} = useTranslation();
const [intakeJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false);
const [markAptArrived] = useMutation(MARK_APPOINTMENT_ARRIVED);
const [markLatestAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_ARRIVED);
const [updateOwner] = useMutation(UPDATE_OWNER);
const { jobId } = useParams();
const history = useHistory();
const search = queryString.parse(useLocation().search);
const [form] = Form.useForm();
const {jobId} = useParams();
const history = useNavigate();
const search = queryString.parse(useLocation().search);
const [form] = Form.useForm();
const handleFinish = async (values) => {
setLoading(true);
logImEXEvent("job_complete_intake");
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" && {
production_vars: {
...(job ? job.production_vars : {}),
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" && {
production_vars: {
...(job ? job.production_vars : {}),
note:
values.production_vars &&
values.production_vars.note &&
values.production_vars.note !== ""
? values &&
values.production_vars &&
values.production_vars.note
: job && job.production_vars && job.production_vars.note,
note:
values.production_vars &&
values.production_vars.note &&
values.production_vars.note !== ""
? values &&
values.production_vars &&
values.production_vars.note
: job && job.production_vars && job.production_vars.note,
},
}),
...(type === "intake" && {
scheduled_completion: values.scheduled_completion,
}),
...(type === "intake" &&
bodyshop.intakechecklist &&
bodyshop.intakechecklist.next_contact_hours &&
bodyshop.intakechecklist.next_contact_hours > 0 && {
date_next_contact: dayjs().add(
bodyshop.intakechecklist.next_contact_hours,
"hour"
),
}),
...(type === "deliver" && {
actual_completion: values.actual_completion,
}),
[(type === "intake" && "intakechecklist") ||
(type === "deliver" && "deliverchecklist")]: {
...values,
form: formItems.map((fi) => {
return {
...fi,
value: values[fi.name],
};
}),
completed_by: currentUser.email,
completed_at: new Date(),
},
...(type === "intake" &&
values.scheduled_delivery && {
scheduled_delivery: values.scheduled_delivery,
}),
...(type === "deliver" && {
scheduled_delivery: values.scheduled_delivery,
actual_delivery: values.actual_delivery,
}),
...(type === "deliver" &&
values.removeFromProduction && {
inproduction: false,
}),
},
},
}),
...(type === "intake" && {
scheduled_completion: values.scheduled_completion,
}),
...(type === "intake" &&
bodyshop.intakechecklist &&
bodyshop.intakechecklist.next_contact_hours &&
bodyshop.intakechecklist.next_contact_hours > 0 && {
date_next_contact: moment().add(
bodyshop.intakechecklist.next_contact_hours,
"hours"
),
}),
...(type === "deliver" && {
actual_completion: values.actual_completion,
}),
[(type === "intake" && "intakechecklist") ||
(type === "deliver" && "deliverchecklist")]: {
...values,
form: formItems.map((fi) => {
return {
...fi,
value: values[fi.name],
};
}),
completed_by: currentUser.email,
completed_at: new Date(),
},
...(type === "intake" &&
values.scheduled_delivery && {
scheduled_delivery: values.scheduled_delivery,
}),
...(type === "deliver" && {
scheduled_delivery: values.scheduled_delivery,
actual_delivery: values.actual_delivery,
}),
...(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),
}),
});
}
} else if (type === "intake" && !search.appointmentId) {
const appUpdate = await markLatestAptArrived({
variables: { jobId: jobId },
});
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),
}),
});
}
}
if (!!appUpdate.errors) {
notification["error"]({
message: t("checklist.errors.complete", {
error: JSON.stringify(result.errors),
}),
});
}
} else if (type === "intake" && !search.appointmentId) {
const appUpdate = await markLatestAptArrived({
variables: {jobId: jobId},
});
if (type === "intake" && job.owner && job.owner.id) {
//Updae Owner Allow to Text
const updateOwnerResult = await updateOwner({
variables: {
ownerId: job.owner.id,
owner: { allow_text_message: values.allow_text_message },
},
});
if (!!appUpdate.errors) {
notification["error"]({
message: t("checklist.errors.complete", {
error: JSON.stringify(result.errors),
}),
});
}
}
if (!!updateOwnerResult.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}`);
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.jobchecklist(
type,
(type === "deliver" && values.removeFromProduction && false) ||
(type === "intake" && values.addToProduction),
(type === "intake" && bodyshop.md_ro_statuses.default_arrived) ||
(type === "deliver" && bodyshop.md_ro_statuses.default_delivered)
),
});
} else {
notification["error"]({
message: t("checklist.errors.complete", {
error: JSON.stringify(result.errors),
}),
});
}
};
return (
<Card
title={t("checklist.labels.checklist")}
extra={
!readOnly && (
<Button
loading={loading}
type="primary"
onClick={() => form.submit()}
>
{t("general.actions.submit")}
</Button>
)
}
>
<Form
form={form}
onFinish={handleFinish}
initialValues={{
...(type === "intake" && {
addToProduction: true,
allow_text_message: job.owner && job.owner.allow_text_message,
scheduled_completion:
(job &&
job.scheduled_completion &&
moment(job.scheduled_completion)) ||
(job &&
job.labhrs &&
job.larhrs &&
moment().businessAdd(
(job.labhrs.aggregate.sum.mod_lb_hrs ||
0 + job.larhrs.aggregate.sum.mod_lb_hrs ||
0) / bodyshop.target_touchtime,
"days"
)),
scheduled_delivery:
job.scheduled_delivery && moment(job.scheduled_delivery),
production_vars: job.production_vars,
}),
...(type === "deliver" && {
removeFromProduction: true,
actual_completion:
job && job.actual_completion && moment(job.actual_completion),
actual_delivery:
job && job.actual_delivery && moment(job.actual_delivery),
}),
...formItems
.filter((fi) => fi.value)
.reduce((acc, fi) => {
acc[fi.name] = fi.value;
return acc;
}, {}),
}}
>
<ConfigFormComponents componentList={formItems} readOnly={readOnly} />
{type === "intake" && (
<div>
<Form.Item
name="addToProduction"
valuePropName="checked"
label={t("checklist.labels.addtoproduction")}
disabled={readOnly}
>
<Switch disabled={readOnly} />
</Form.Item>
<Form.Item
name="allow_text_message"
valuePropName="checked"
label={t("checklist.labels.allow_text_message")}
disabled={readOnly}
>
<Switch disabled={readOnly} />
</Form.Item>
<Form.Item
name="scheduled_completion"
label={t("jobs.fields.scheduled_completion")}
disabled={readOnly}
rules={[
{
required: true,
//message: t("general.validation.required"),
if (type === "intake" && job.owner && job.owner.id) {
//Updae Owner Allow to Text
const updateOwnerResult = await updateOwner({
variables: {
ownerId: job.owner.id,
owner: {allow_text_message: values.allow_text_message},
},
]}
});
if (!!updateOwnerResult.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(`/manage/jobs/${jobId}`);
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.jobchecklist(
type,
(type === "deliver" && values.removeFromProduction && false) ||
(type === "intake" && values.addToProduction),
(type === "intake" && bodyshop.md_ro_statuses.default_arrived) ||
(type === "deliver" && bodyshop.md_ro_statuses.default_delivered)
),
});
} else {
notification["error"]({
message: t("checklist.errors.complete", {
error: JSON.stringify(result.errors),
}),
});
}
};
return (
<Card
title={t("checklist.labels.checklist")}
extra={
!readOnly && (
<Button
loading={loading}
type="primary"
onClick={() => form.submit()}
>
{t("general.actions.submit")}
</Button>
)
}
>
<Form
form={form}
onFinish={handleFinish}
initialValues={{
...(type === "intake" && {
addToProduction: true,
allow_text_message: job.owner && job.owner.allow_text_message,
scheduled_completion:
(job &&
job.scheduled_completion &&
dayjs(job.scheduled_completion)) ||
(job &&
job.labhrs &&
job.larhrs &&
dayjs().businessDaysAdd(
(job.labhrs.aggregate.sum.mod_lb_hrs ||
0 + job.larhrs.aggregate.sum.mod_lb_hrs ||
0) / bodyshop.target_touchtime,
"day"
)),
scheduled_delivery:
job.scheduled_delivery && dayjs(job.scheduled_delivery),
production_vars: job.production_vars,
}),
...(type === "deliver" && {
removeFromProduction: true,
actual_completion:
job && job.actual_completion && dayjs(job.actual_completion),
actual_delivery:
job && job.actual_delivery && dayjs(job.actual_delivery),
}),
...formItems
.filter((fi) => fi.value)
.reduce((acc, fi) => {
acc[fi.name] = fi.value;
return acc;
}, {}),
}}
>
<DateTimePicker disabled={readOnly} />
</Form.Item>
<Form.Item
name="scheduled_delivery"
label={t("jobs.fields.scheduled_delivery")}
disabled={readOnly}
>
<DateTimePicker disabled={readOnly} />
</Form.Item>
<Form.Item
name={["production_vars", "note"]}
label={t("jobs.fields.production_vars.note")}
disabled={readOnly}
trigger="onChange"
>
<Input.TextArea rows={3} disabled={readOnly} />
</Form.Item>
</div>
)}
{type === "deliver" && (
<div>
<Form.Item
name="actual_completion"
label={t("jobs.fields.actual_completion")}
disabled={readOnly}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<DateTimePicker disabled={readOnly} />
</Form.Item>
<Form.Item
name="actual_delivery"
label={t("jobs.fields.actual_delivery")}
disabled={readOnly}
>
<DateTimePicker disabled={readOnly} />
</Form.Item>
<Form.Item
name="removeFromProduction"
valuePropName="checked"
label={t("checklist.labels.removefromproduction")}
disabled={readOnly}
>
<Switch disabled={readOnly} defaultChecked={true} />
</Form.Item>
</div>
)}
</Form>
</Card>
);
<ConfigFormComponents componentList={formItems} readOnly={readOnly}/>
{type === "intake" && (
<div>
<Form.Item
name="addToProduction"
valuePropName="checked"
label={t("checklist.labels.addtoproduction")}
disabled={readOnly}
>
<Switch disabled={readOnly}/>
</Form.Item>
<Form.Item
name="allow_text_message"
valuePropName="checked"
label={t("checklist.labels.allow_text_message")}
disabled={readOnly}
>
<Switch disabled={readOnly}/>
</Form.Item>
<Form.Item
name="scheduled_completion"
label={t("jobs.fields.scheduled_completion")}
disabled={readOnly}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<DateTimePicker disabled={readOnly}/>
</Form.Item>
<Form.Item
name="scheduled_delivery"
label={t("jobs.fields.scheduled_delivery")}
disabled={readOnly}
>
<DateTimePicker disabled={readOnly}/>
</Form.Item>
<Form.Item
name={["production_vars", "note"]}
label={t("jobs.fields.production_vars.note")}
disabled={readOnly}
trigger="onChange"
>
<Input.TextArea rows={3} disabled={readOnly}/>
</Form.Item>
</div>
)}
{type === "deliver" && (
<div>
<Form.Item
name="actual_completion"
label={t("jobs.fields.actual_completion")}
disabled={readOnly}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<DateTimePicker disabled={readOnly}/>
</Form.Item>
<Form.Item
name="actual_delivery"
label={t("jobs.fields.actual_delivery")}
disabled={readOnly}
>
<DateTimePicker disabled={readOnly}/>
</Form.Item>
<Form.Item
name="removeFromProduction"
valuePropName="checked"
label={t("checklist.labels.removefromproduction")}
disabled={readOnly}
>
<Switch disabled={readOnly} defaultChecked={true}/>
</Form.Item>
</div>
)}
</Form>
</Card>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobChecklistForm);

View File

@@ -1,81 +1,79 @@
import { PrinterFilled } from "@ant-design/icons";
import { Button, Card, List } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { logImEXEvent } from "../../../../firebase/firebase.utils";
import {
GenerateDocument,
GenerateDocuments,
} from "../../../../utils/RenderTemplate";
import { TemplateList } from "../../../../utils/TemplateConstants";
import {PrinterFilled} from "@ant-design/icons";
import {Button, Card, List} from "antd";
import React, {useState} from "react";
import {useTranslation} from "react-i18next";
import {useParams} from "react-router-dom";
import {logImEXEvent} from "../../../../firebase/firebase.utils";
import {GenerateDocument, GenerateDocuments,} from "../../../../utils/RenderTemplate";
import {TemplateList} from "../../../../utils/TemplateConstants";
const TemplateListGenerated = TemplateList();
export default function JobIntakeTemplateList({ templates }) {
const { jobId } = useParams();
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
export default function JobIntakeTemplateList({templates}) {
const {jobId} = useParams();
const {t} = useTranslation();
const [loading, setLoading] = useState(false);
const renderTemplate = async (templateKey) => {
setLoading(true);
logImEXEvent("job_checklist_template_render");
const renderTemplate = async (templateKey) => {
setLoading(true);
logImEXEvent("job_checklist_template_render");
await GenerateDocument(
{
name: templateKey,
variables: { id: jobId },
},
{},
"p"
);
setLoading(false);
};
await GenerateDocument(
{
name: templateKey,
variables: {id: jobId},
},
{},
"p"
);
setLoading(false);
};
const renderAllTemplates = async () => {
logImEXEvent("checklist_render_all_templates");
setLoading(true);
const renderAllTemplates = async () => {
logImEXEvent("checklist_render_all_templates");
setLoading(true);
await GenerateDocuments(
templates.map((key) => {
return { name: key, variables: { id: jobId } };
})
);
setLoading(false);
};
await GenerateDocuments(
templates.map((key) => {
return {name: key, variables: {id: jobId}};
})
);
setLoading(false);
};
return (
<Card
title={t("jobs.labels.checklistdocuments")}
extra={
<Button onClick={renderAllTemplates} loading={loading}>
{t("checklist.actions.printall")}
</Button>
}
>
<List
itemLayout="horizontal"
dataSource={templates}
renderItem={(template) => (
<List.Item
actions={[
<Button
loading={loading}
onClick={() => renderTemplate(template)}
>
<PrinterFilled />
</Button>,
]}
>
<List.Item.Meta
title={
TemplateListGenerated[template] &&
TemplateListGenerated[template].title
}
// description={renderTemplateList(template.templates)}
return (
<Card
title={t("jobs.labels.checklistdocuments")}
extra={
<Button onClick={renderAllTemplates} loading={loading}>
{t("checklist.actions.printall")}
</Button>
}
>
<List
itemLayout="horizontal"
dataSource={templates}
renderItem={(template) => (
<List.Item
actions={[
<Button
loading={loading}
onClick={() => renderTemplate(template)}
>
<PrinterFilled/>
</Button>,
]}
>
<List.Item.Meta
title={
TemplateListGenerated[template] &&
TemplateListGenerated[template].title
}
// description={renderTemplateList(template.templates)}
/>
</List.Item>
)}
/>
</List.Item>
)}
/>
</Card>
);
</Card>
);
}

View File

@@ -1,11 +1,11 @@
import React from "react";
import ConfigFormComponents from "../config-form-components/config-form-components.component";
export default function JobChecklistDisplay({ checklist }) {
if (!checklist) return <div></div>;
return (
<div>
<ConfigFormComponents readOnly componentList={checklist} />
</div>
);
export default function JobChecklistDisplay({checklist}) {
if (!checklist) return <div></div>;
return (
<div>
<ConfigFormComponents readOnly componentList={checklist}/>
</div>
);
}

View File

@@ -1,18 +1,19 @@
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, job }) {
const { form, templates } = checklistConfig;
import {Col, Row} from "antd";
return (
<Row gutter={[16, 16]}>
<Col sm={24} md={8}>
<JobChecklistTemplateList templates={templates} type={type} />
</Col>
<Col sm={24} md={16}>
<JobChecklistForm formItems={form} type={type} job={job} />
</Col>
</Row>
);
export default function JobIntakeComponent({checklistConfig, type, job}) {
const {form, templates} = checklistConfig;
return (
<Row gutter={[16, 16]}>
<Col sm={24} md={8}>
<JobChecklistTemplateList templates={templates} type={type}/>
</Col>
<Col sm={24} md={16}>
<JobChecklistForm formItems={form} type={type} job={job}/>
</Col>
</Row>
);
}