- Merge client update into test-beta
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -1,31 +1,25 @@
|
||||
import Icon, {
|
||||
BarsOutlined,
|
||||
CalendarFilled,
|
||||
DollarCircleOutlined,
|
||||
FileImageFilled,
|
||||
HistoryOutlined,
|
||||
PrinterFilled,
|
||||
SyncOutlined,
|
||||
ToolFilled,
|
||||
BarsOutlined,
|
||||
CalendarFilled,
|
||||
DollarCircleOutlined,
|
||||
FileImageFilled,
|
||||
HistoryOutlined,
|
||||
PrinterFilled,
|
||||
SyncOutlined,
|
||||
ToolFilled,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Form,
|
||||
PageHeader,
|
||||
Space,
|
||||
Tabs,
|
||||
notification,
|
||||
} from "antd";
|
||||
import {Button, Divider, Form, notification, Space, Tabs,} from "antd";
|
||||
import {PageHeader} from "@ant-design/pro-layout";
|
||||
|
||||
import Axios from "axios";
|
||||
import moment from "moment";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaHardHat, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
|
||||
import { connect } from "react-redux";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {FaHardHat, FaRegStickyNote, FaShieldAlt} from "react-icons/fa";
|
||||
import {connect} from "react-redux";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
|
||||
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
|
||||
@@ -43,409 +37,364 @@ import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail
|
||||
import JobsDetailRates from "../../components/jobs-detail-rates/jobs-detail-rates.component";
|
||||
import JobsDetailTotals from "../../components/jobs-detail-totals/jobs-detail-totals.component";
|
||||
import JobsDocumentsGalleryContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container";
|
||||
import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import JobsDocumentsLocalGallery
|
||||
from "../../components/jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
|
||||
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import {insertAuditTrail} from "../../redux/application/application.actions";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||
import _ from "lodash";
|
||||
import JobProfileDataWarning from "../../components/job-profile-data-warning/job-profile-data-warning.component";
|
||||
import { DateTimeFormat } from "./../../utils/DateFormatter";
|
||||
import {DateTimeFormat} from "./../../utils/DateFormatter";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||
insertAuditTrail: ({ jobid, operation }) =>
|
||||
dispatch(insertAuditTrail({ jobid, operation })),
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(setModalContext({context: context, modal: "printCenter"})),
|
||||
insertAuditTrail: ({jobid, operation}) =>
|
||||
dispatch(insertAuditTrail({jobid, operation})),
|
||||
});
|
||||
|
||||
export function JobsDetailPage({
|
||||
bodyshop,
|
||||
setPrintCenterContext,
|
||||
jobRO,
|
||||
job,
|
||||
mutationUpdateJob,
|
||||
handleSubmit,
|
||||
insertAuditTrail,
|
||||
refetch,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const history = useHistory();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const search = queryString.parse(useLocation().search);
|
||||
bodyshop,
|
||||
setPrintCenterContext,
|
||||
jobRO,
|
||||
job,
|
||||
mutationUpdateJob,
|
||||
handleSubmit,
|
||||
insertAuditTrail,
|
||||
refetch,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const history = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const formItemLayout = {
|
||||
layout: "vertical",
|
||||
};
|
||||
|
||||
const formItemLayout = {
|
||||
layout: "vertical",
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
//form.setFieldsValue(transormJobToForm(job));
|
||||
form.resetFields();
|
||||
}, [form, job]);
|
||||
|
||||
//useKeyboardSaveShortcut(form.submit);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setLoading(true);
|
||||
|
||||
const result = await mutationUpdateJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {
|
||||
...UndefinedToNull(values, [
|
||||
"alt_transport",
|
||||
"category",
|
||||
"referral_source",
|
||||
]),
|
||||
// The union and spread is required to keep values coming in from the estimating system that aren't displayed.
|
||||
parts_tax_rates: _.union(
|
||||
Object.keys(job.parts_tax_rates),
|
||||
Object.keys(values.parts_tax_rates)
|
||||
).reduce((acc, val) => {
|
||||
acc[val] = {
|
||||
...job.parts_tax_rates[val],
|
||||
...values.parts_tax_rates[val],
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
materials: _.union(
|
||||
Object.keys(job.materials),
|
||||
Object.keys(values.materials)
|
||||
).reduce((acc, val) => {
|
||||
acc[val] = {
|
||||
...job.materials[val],
|
||||
...values.materials[val],
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
cieca_pfl: _.union(
|
||||
Object.keys(job.cieca_pfl),
|
||||
Object.keys(values.cieca_pfl)
|
||||
).reduce((acc, val) => {
|
||||
acc[val] = {
|
||||
...job.cieca_pfl[val],
|
||||
...values.cieca_pfl[val],
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
cieca_pfo: { ...job.cieca_pfo, ...values.cieca_pfo },
|
||||
},
|
||||
},
|
||||
});
|
||||
try {
|
||||
const newTotals = await Axios.post("/job/totalsssu", {
|
||||
id: job.id,
|
||||
});
|
||||
|
||||
if (newTotals.status !== 200 || result.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.totalscalc"),
|
||||
});
|
||||
} else {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.savetitle"),
|
||||
});
|
||||
const changedAuditFields = form.getFieldsValue(
|
||||
[
|
||||
"scheduled_in",
|
||||
"actual_in",
|
||||
"scheduled_completion",
|
||||
"actual_completion",
|
||||
"scheduled_delivery",
|
||||
"actual_delivery",
|
||||
"date_invoiced",
|
||||
"ins_co_nm",
|
||||
"ded_amt",
|
||||
"ded_status",
|
||||
"date_exported",
|
||||
"special_coverage_policy",
|
||||
"ca_gst_registrant",
|
||||
"ca_bc_pvrt",
|
||||
"scheduled_in",
|
||||
"rate_la1",
|
||||
"rate_la2",
|
||||
"rate_la3",
|
||||
"rate_la4",
|
||||
"rate_laa",
|
||||
"rate_lab",
|
||||
"rate_lad",
|
||||
"rate_lae",
|
||||
"rate_laf",
|
||||
"rate_lag",
|
||||
"rate_lam",
|
||||
"rate_lar",
|
||||
"rate_las",
|
||||
"rate_lau",
|
||||
"rate_ma2s",
|
||||
"rate_ma2t",
|
||||
"rate_ma3s",
|
||||
"rate_mabl",
|
||||
"rate_macs",
|
||||
"rate_mapa",
|
||||
"rate_mahw",
|
||||
"rate_mash",
|
||||
"rate_matd",
|
||||
],
|
||||
(meta) => meta && meta.touched
|
||||
);
|
||||
|
||||
Object.keys(changedAuditFields).forEach((key) => {
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.jobfieldchange(
|
||||
key,
|
||||
changedAuditFields[key] instanceof moment
|
||||
? DateTimeFormat(changedAuditFields[key])
|
||||
: changedAuditFields[key]
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
await refetch();
|
||||
form.setFieldsValue(transormJobToForm(job));
|
||||
useEffect(() => {
|
||||
//form.setFieldsValue(transormJobToForm(job));
|
||||
form.resetFields();
|
||||
}
|
||||
} catch (error) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.totalscalc"),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
}, [form, job]);
|
||||
|
||||
const menuExtra = (
|
||||
<Space wrap>
|
||||
<Button
|
||||
onClick={() => {
|
||||
refetch();
|
||||
}}
|
||||
key="refresh"
|
||||
>
|
||||
<SyncOutlined />
|
||||
{t("general.labels.refresh")}
|
||||
</Button>
|
||||
<JobsChangeStatus job={job} />
|
||||
<JobSyncButton job={job} />
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPrintCenterContext({
|
||||
actions: { refetch: refetch },
|
||||
context: {
|
||||
id: job.id,
|
||||
job: job,
|
||||
type: "job",
|
||||
//useKeyboardSaveShortcut(form.submit);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setLoading(true);
|
||||
|
||||
const result = await mutationUpdateJob({
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {
|
||||
...UndefinedToNull(values, [
|
||||
"alt_transport",
|
||||
"category",
|
||||
"referral_source",
|
||||
]),
|
||||
// The union and spread is required to keep values coming in from the estimating system that aren't displayed.
|
||||
parts_tax_rates: _.union(
|
||||
Object.keys(job.parts_tax_rates),
|
||||
Object.keys(values.parts_tax_rates)
|
||||
).reduce((acc, val) => {
|
||||
acc[val] = {
|
||||
...job.parts_tax_rates[val],
|
||||
...values.parts_tax_rates[val],
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
materials: _.union(
|
||||
Object.keys(job.materials),
|
||||
Object.keys(values.materials)
|
||||
).reduce((acc, val) => {
|
||||
acc[val] = {
|
||||
...job.materials[val],
|
||||
...values.materials[val],
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
cieca_pfl: _.union(
|
||||
Object.keys(job.cieca_pfl),
|
||||
Object.keys(values.cieca_pfl)
|
||||
).reduce((acc, val) => {
|
||||
acc[val] = {
|
||||
...job.cieca_pfl[val],
|
||||
...values.cieca_pfl[val],
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
cieca_pfo: {...job.cieca_pfo, ...values.cieca_pfo},
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
key="printing"
|
||||
>
|
||||
<PrinterFilled />
|
||||
{t("jobs.actions.printCenter")}
|
||||
</Button>
|
||||
<JobsConvertButton
|
||||
job={job}
|
||||
refetch={refetch}
|
||||
parentFormIsFieldsTouched={form.isFieldsTouched}
|
||||
/>
|
||||
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch} />
|
||||
<Button
|
||||
type="primary"
|
||||
loading={loading}
|
||||
disabled={jobRO}
|
||||
onClick={() => form.submit()}
|
||||
>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
});
|
||||
try {
|
||||
const newTotals = await Axios.post("/job/totalsssu", {
|
||||
id: job.id,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ScheduleJobModalContainer />
|
||||
<JobReconciliationModal />
|
||||
<JobLineUpsertModalContainer />
|
||||
<NoteUpsertModalComponent />
|
||||
<Form
|
||||
form={form}
|
||||
name="JobDetailForm"
|
||||
onFinish={handleFinish}
|
||||
{...formItemLayout}
|
||||
autoComplete={"off"}
|
||||
initialValues={transormJobToForm(job)}
|
||||
>
|
||||
<PageHeader
|
||||
// onBack={() => window.history.back()}
|
||||
title={job.ro_number || t("general.labels.na")}
|
||||
extra={menuExtra}
|
||||
/>
|
||||
<JobsDetailHeader job={job} />
|
||||
<Divider type="horizontal" />
|
||||
<JobProfileDataWarning job={job} />
|
||||
<FormFieldsChanged form={form} />
|
||||
<Tabs
|
||||
defaultActiveKey={search.tab}
|
||||
onChange={(key) => history.push({ search: `?tab=${key}` })}
|
||||
tabBarStyle={{ fontWeight: "bold", borderBottom: "10px" }}
|
||||
>
|
||||
<Tabs.TabPane
|
||||
forceRender
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaShieldAlt} />
|
||||
{t("menus.jobsdetail.general")}
|
||||
</span>
|
||||
if (newTotals.status !== 200 || result.errors) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.totalscalc"),
|
||||
});
|
||||
} else {
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.savetitle"),
|
||||
});
|
||||
const changedAuditFields = form.getFieldsValue(
|
||||
[
|
||||
"scheduled_in",
|
||||
"actual_in",
|
||||
"scheduled_completion",
|
||||
"actual_completion",
|
||||
"scheduled_delivery",
|
||||
"actual_delivery",
|
||||
"date_invoiced",
|
||||
"ins_co_nm",
|
||||
"ded_amt",
|
||||
"ded_status",
|
||||
"date_exported",
|
||||
"special_coverage_policy",
|
||||
"ca_gst_registrant",
|
||||
"ca_bc_pvrt",
|
||||
"scheduled_in",
|
||||
"rate_la1",
|
||||
"rate_la2",
|
||||
"rate_la3",
|
||||
"rate_la4",
|
||||
"rate_laa",
|
||||
"rate_lab",
|
||||
"rate_lad",
|
||||
"rate_lae",
|
||||
"rate_laf",
|
||||
"rate_lag",
|
||||
"rate_lam",
|
||||
"rate_lar",
|
||||
"rate_las",
|
||||
"rate_lau",
|
||||
"rate_ma2s",
|
||||
"rate_ma2t",
|
||||
"rate_ma3s",
|
||||
"rate_mabl",
|
||||
"rate_macs",
|
||||
"rate_mapa",
|
||||
"rate_mahw",
|
||||
"rate_mash",
|
||||
"rate_matd",
|
||||
],
|
||||
(meta) => meta && meta.touched
|
||||
);
|
||||
|
||||
Object.keys(changedAuditFields).forEach((key) => {
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.jobfieldchange(
|
||||
key,
|
||||
changedAuditFields[key] instanceof dayjs
|
||||
? DateTimeFormat(changedAuditFields[key])
|
||||
: changedAuditFields[key]
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
await refetch();
|
||||
form.setFieldsValue(transormJobToForm(job));
|
||||
form.resetFields();
|
||||
}
|
||||
key="general"
|
||||
>
|
||||
<JobsDetailGeneral job={job} form={form} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
forceRender
|
||||
tab={
|
||||
<span>
|
||||
<BarsOutlined />
|
||||
{t("menus.jobsdetail.repairdata")}
|
||||
</span>
|
||||
}
|
||||
key="repairdata"
|
||||
>
|
||||
<JobsLinesContainer
|
||||
job={job}
|
||||
joblines={job.joblines}
|
||||
refetch={refetch}
|
||||
form={form}
|
||||
} catch (error) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.totalscalc"),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const menuExtra = (
|
||||
<Space wrap>
|
||||
<Button
|
||||
onClick={() => {
|
||||
refetch();
|
||||
}}
|
||||
key="refresh"
|
||||
>
|
||||
<SyncOutlined/>
|
||||
{t("general.labels.refresh")}
|
||||
</Button>
|
||||
<JobsChangeStatus job={job}/>
|
||||
<JobSyncButton job={job}/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setPrintCenterContext({
|
||||
actions: {refetch: refetch},
|
||||
context: {
|
||||
id: job.id,
|
||||
job: job,
|
||||
type: "job",
|
||||
},
|
||||
});
|
||||
}}
|
||||
key="printing"
|
||||
>
|
||||
<PrinterFilled/>
|
||||
{t("jobs.actions.printCenter")}
|
||||
</Button>
|
||||
<JobsConvertButton
|
||||
job={job}
|
||||
refetch={refetch}
|
||||
parentFormIsFieldsTouched={form.isFieldsTouched}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
forceRender
|
||||
tab={
|
||||
<span>
|
||||
<DollarCircleOutlined />
|
||||
{t("menus.jobsdetail.rates")}
|
||||
</span>
|
||||
}
|
||||
key="rates"
|
||||
>
|
||||
<JobsDetailRates job={job} form={form} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<DollarCircleOutlined />
|
||||
{t("menus.jobsdetail.totals")}
|
||||
</span>
|
||||
}
|
||||
key="totals"
|
||||
>
|
||||
<JobsDetailTotals job={job} refetch={refetch} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<ToolFilled />
|
||||
{t("menus.jobsdetail.partssublet")}
|
||||
</span>
|
||||
}
|
||||
key="partssublet"
|
||||
>
|
||||
<JobsDetailPliContainer job={job} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaHardHat} />
|
||||
{t("menus.jobsdetail.labor")}
|
||||
</span>
|
||||
}
|
||||
key="labor"
|
||||
>
|
||||
<JobsDetailLaborContainer job={job} jobId={job.id} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
forceRender
|
||||
tab={
|
||||
<span>
|
||||
<CalendarFilled />
|
||||
{t("menus.jobsdetail.dates")}
|
||||
</span>
|
||||
}
|
||||
key="dates"
|
||||
>
|
||||
<JobsDetailDatesComponent job={job} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<FileImageFilled />
|
||||
{t("jobs.labels.documents")}
|
||||
</span>
|
||||
}
|
||||
key="documents"
|
||||
>
|
||||
{bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery job={job} />
|
||||
) : (
|
||||
<JobsDocumentsGalleryContainer jobId={job.id} />
|
||||
)}
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<Icon component={FaRegStickyNote} />
|
||||
{t("jobs.labels.notes")}
|
||||
</span>
|
||||
}
|
||||
key="notes"
|
||||
>
|
||||
<JobNotesContainer jobId={job.id} />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={
|
||||
<span>
|
||||
<HistoryOutlined />
|
||||
{t("jobs.labels.audit")}
|
||||
</span>
|
||||
}
|
||||
key="audit"
|
||||
>
|
||||
<JobAuditTrail jobId={job.id} />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch}/>
|
||||
<Button
|
||||
type="primary"
|
||||
loading={loading}
|
||||
disabled={jobRO}
|
||||
onClick={() => form.submit()}
|
||||
>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ScheduleJobModalContainer/>
|
||||
<JobReconciliationModal/>
|
||||
<JobLineUpsertModalContainer/>
|
||||
<NoteUpsertModalComponent/>
|
||||
<Form
|
||||
form={form}
|
||||
name="JobDetailForm"
|
||||
onFinish={handleFinish}
|
||||
{...formItemLayout}
|
||||
autoComplete={"off"}
|
||||
initialValues={transormJobToForm(job)}
|
||||
>
|
||||
<PageHeader
|
||||
// onBack={() => window.history.back()}
|
||||
title={job.ro_number || t("general.labels.na")}
|
||||
extra={menuExtra}
|
||||
/>
|
||||
<JobsDetailHeader job={job}/>
|
||||
<Divider type="horizontal"/>
|
||||
<JobProfileDataWarning job={job}/>
|
||||
<FormFieldsChanged form={form}/>
|
||||
<Tabs
|
||||
defaultActiveKey={search.tab}
|
||||
onChange={(key) => history({search: `?tab=${key}`})}
|
||||
tabBarStyle={{fontWeight: "bold", borderBottom: "10px"}}
|
||||
items={[
|
||||
{
|
||||
key: "general",
|
||||
icon: <Icon component={FaShieldAlt}/>,
|
||||
label: t("menus.jobsdetail.general"),
|
||||
forceRender: true,
|
||||
children: <JobsDetailGeneral job={job} form={form}/>,
|
||||
},
|
||||
{
|
||||
key: "repairdata",
|
||||
icon: <BarsOutlined/>,
|
||||
label: t("menus.jobsdetail.repairdata"),
|
||||
forceRender: true,
|
||||
children: (
|
||||
<JobsLinesContainer
|
||||
job={job}
|
||||
joblines={job.joblines}
|
||||
refetch={refetch}
|
||||
form={form}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "rates",
|
||||
icon: <DollarCircleOutlined/>,
|
||||
label: t("menus.jobsdetail.rates"),
|
||||
forceRender: true,
|
||||
children: <JobsDetailRates job={job} form={form}/>,
|
||||
},
|
||||
{
|
||||
key: "totals",
|
||||
icon: <DollarCircleOutlined/>,
|
||||
label: t("menus.jobsdetail.totals"),
|
||||
children: <JobsDetailTotals job={job} refetch={refetch}/>,
|
||||
},
|
||||
{
|
||||
key: "partssublet",
|
||||
icon: <ToolFilled/>,
|
||||
label: t("menus.jobsdetail.partssublet"),
|
||||
children: <JobsDetailPliContainer job={job}/>,
|
||||
},
|
||||
{
|
||||
key: "labor",
|
||||
icon: <Icon component={FaHardHat}/>,
|
||||
label: t("menus.jobsdetail.labor"),
|
||||
children: <JobsDetailLaborContainer job={job} jobId={job.id}/>,
|
||||
},
|
||||
{
|
||||
key: "dates",
|
||||
icon: <CalendarFilled/>,
|
||||
label: t("menus.jobsdetail.dates"),
|
||||
forceRender: true,
|
||||
children: <JobsDetailDatesComponent job={job}/>,
|
||||
},
|
||||
{
|
||||
key: "documents",
|
||||
icon: <FileImageFilled/>,
|
||||
label: t("jobs.labels.documents"),
|
||||
children: bodyshop.uselocalmediaserver ? (
|
||||
<JobsDocumentsLocalGallery job={job}/>
|
||||
) : (
|
||||
<JobsDocumentsGalleryContainer jobId={job.id}/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "notes",
|
||||
icon: <Icon component={FaRegStickyNote}/>,
|
||||
label: t("jobs.labels.notes"),
|
||||
children: <JobNotesContainer jobId={job.id}/>,
|
||||
},
|
||||
{
|
||||
key: "audit",
|
||||
icon: <HistoryOutlined/>,
|
||||
label: t("jobs.labels.audit"),
|
||||
children: <JobAuditTrail jobId={job.id}/>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
|
||||
|
||||
const transormJobToForm = (job) => {
|
||||
Object.keys(job.parts_tax_rates).forEach((parttype) => {
|
||||
Object.keys(job.parts_tax_rates[parttype]).forEach((key) => {
|
||||
if (key.includes("tx_in")) {
|
||||
if (
|
||||
job.parts_tax_rates[parttype][key] === "Y" ||
|
||||
job.parts_tax_rates[parttype][key] === true
|
||||
) {
|
||||
job.parts_tax_rates[parttype][key] = true;
|
||||
} else {
|
||||
job.parts_tax_rates[parttype][key] = false;
|
||||
}
|
||||
}
|
||||
Object.keys(job.parts_tax_rates).forEach((parttype) => {
|
||||
Object.keys(job.parts_tax_rates[parttype]).forEach((key) => {
|
||||
if (key.includes("tx_in")) {
|
||||
if (
|
||||
job.parts_tax_rates[parttype][key] === "Y" ||
|
||||
job.parts_tax_rates[parttype][key] === true
|
||||
) {
|
||||
job.parts_tax_rates[parttype][key] = true;
|
||||
} else {
|
||||
job.parts_tax_rates[parttype][key] = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
...job,
|
||||
loss_date: job.loss_date ? moment(job.loss_date) : null,
|
||||
date_estimated: job.date_estimated ? moment(job.date_estimated) : null,
|
||||
};
|
||||
return {
|
||||
...job,
|
||||
loss_date: job.loss_date ? dayjs(job.loss_date) : null,
|
||||
date_estimated: job.date_estimated ? dayjs(job.date_estimated) : null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,121 +1,121 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {useMutation, useQuery} from "@apollo/client";
|
||||
import React, {useEffect} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {useParams} from 'react-router-dom';
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import AlertComponent from "../../components/alert/alert.component";
|
||||
import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
|
||||
import NotFound from "../../components/not-found/not-found.component";
|
||||
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
|
||||
import {OwnerNameDisplayFunction} from "../../components/owner-name-display/owner-name-display.component";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import { GET_JOB_BY_PK, UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import {GET_JOB_BY_PK, UPDATE_JOB} from "../../graphql/jobs.queries";
|
||||
import {
|
||||
addRecentItem,
|
||||
setBreadcrumbs,
|
||||
setJobReadOnly,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { CreateRecentItem } from "../../utils/create-recent-item";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import {CreateRecentItem} from "../../utils/create-recent-item";
|
||||
import IsJobReadOnly from "../../utils/jobReadOnly";
|
||||
import JobsDetailPage from "./jobs-detail.page.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
addRecentItem: (item) => dispatch(addRecentItem(item)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
setJobReadOnly: (bool) => dispatch(setJobReadOnly(bool)),
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
addRecentItem: (item) => dispatch(addRecentItem(item)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
setJobReadOnly: (bool) => dispatch(setJobReadOnly(bool)),
|
||||
});
|
||||
|
||||
function JobsDetailPageContainer({
|
||||
bodyshop,
|
||||
match,
|
||||
setBreadcrumbs,
|
||||
addRecentItem,
|
||||
setSelectedHeader,
|
||||
setJobReadOnly,
|
||||
}) {
|
||||
const { jobId } = match.params;
|
||||
const { t } = useTranslation();
|
||||
setBreadcrumbs,
|
||||
addRecentItem,
|
||||
setSelectedHeader,
|
||||
setJobReadOnly,
|
||||
}) {
|
||||
const {jobId} = useParams();
|
||||
const {t} = useTranslation();
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
|
||||
variables: { id: jobId },
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
const [mutationUpdateJob] = useMutation(UPDATE_JOB);
|
||||
const {loading, error, data, refetch} = useQuery(GET_JOB_BY_PK, {
|
||||
variables: {id: jobId},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
const [mutationUpdateJob] = useMutation(UPDATE_JOB);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedHeader("activejobs");
|
||||
document.title = loading
|
||||
? t("titles.app")
|
||||
: error
|
||||
? t("titles.app")
|
||||
: t("titles.jobsdetail", {
|
||||
ro_number:
|
||||
(data.jobs_by_pk && data.jobs_by_pk.ro_number) ||
|
||||
t("general.labels.na"),
|
||||
});
|
||||
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) ||
|
||||
t("general.labels.na"),
|
||||
}),
|
||||
},
|
||||
useEffect(() => {
|
||||
setSelectedHeader("activejobs");
|
||||
document.title = loading
|
||||
? t("titles.app")
|
||||
: error
|
||||
? t("titles.app")
|
||||
: t("titles.jobsdetail", {
|
||||
ro_number:
|
||||
(data.jobs_by_pk && data.jobs_by_pk.ro_number) ||
|
||||
t("general.labels.na"),
|
||||
});
|
||||
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) ||
|
||||
t("general.labels.na"),
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
if (data && data.jobs_by_pk) {
|
||||
setJobReadOnly(IsJobReadOnly(data.jobs_by_pk));
|
||||
|
||||
addRecentItem(
|
||||
CreateRecentItem(
|
||||
jobId,
|
||||
"job",
|
||||
|
||||
`${
|
||||
data.jobs_by_pk.ro_number || t("general.labels.na")
|
||||
} | ${OwnerNameDisplayFunction(data.jobs_by_pk)}`,
|
||||
`/manage/jobs/${jobId}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [
|
||||
loading,
|
||||
data,
|
||||
t,
|
||||
error,
|
||||
setBreadcrumbs,
|
||||
jobId,
|
||||
addRecentItem,
|
||||
setSelectedHeader,
|
||||
setJobReadOnly,
|
||||
]);
|
||||
|
||||
if (data && data.jobs_by_pk) {
|
||||
setJobReadOnly(IsJobReadOnly(data.jobs_by_pk));
|
||||
if (loading) return <SpinComponent/>;
|
||||
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||
if (!!!data.jobs_by_pk) return <NotFound/>;
|
||||
|
||||
addRecentItem(
|
||||
CreateRecentItem(
|
||||
jobId,
|
||||
"job",
|
||||
|
||||
`${
|
||||
data.jobs_by_pk.ro_number || t("general.labels.na")
|
||||
} | ${OwnerNameDisplayFunction(data.jobs_by_pk)}`,
|
||||
`/manage/jobs/${jobId}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [
|
||||
loading,
|
||||
data,
|
||||
t,
|
||||
error,
|
||||
setBreadcrumbs,
|
||||
jobId,
|
||||
addRecentItem,
|
||||
setSelectedHeader,
|
||||
setJobReadOnly,
|
||||
]);
|
||||
|
||||
if (loading) return <SpinComponent />;
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
if (!!!data.jobs_by_pk) return <NotFound />;
|
||||
|
||||
return data.jobs_by_pk ? (
|
||||
<RbacWrapper action="jobs:detail">
|
||||
<JobsDetailPage
|
||||
job={data.jobs_by_pk}
|
||||
mutationUpdateJob={mutationUpdateJob}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</RbacWrapper>
|
||||
) : (
|
||||
<AlertComponent message={t("jobs.errors.noaccess")} type="error" />
|
||||
);
|
||||
return data.jobs_by_pk ? (
|
||||
<RbacWrapper action="jobs:detail">
|
||||
<JobsDetailPage
|
||||
job={data.jobs_by_pk}
|
||||
mutationUpdateJob={mutationUpdateJob}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</RbacWrapper>
|
||||
) : (
|
||||
<AlertComponent message={t("jobs.errors.noaccess")} type="error"/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobsDetailPageContainer);
|
||||
|
||||
Reference in New Issue
Block a user