IO-3255 Clean up front end components for pm.

This commit is contained in:
Patrick Fic
2025-06-23 14:00:25 -07:00
parent 09e1887609
commit cbb6c43ec3
17 changed files with 588 additions and 414 deletions

View File

@@ -0,0 +1,73 @@
import { AlertOutlined, BulbOutlined } from "@ant-design/icons";
import { Button, Layout, Space } from "antd";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import WssStatusDisplayComponent from "../../components/wss-status-display/wss-status-display.component.jsx";
import { addAlerts } from "../../redux/application/application.actions.js";
import { selectAlerts } from "../../redux/application/application.selectors.js";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
const { Footer } = Layout;
const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict,
bodyshop: selectBodyshop,
alerts: selectAlerts
});
const mapDispatchToProps = (dispatch) => ({
setAlerts: (alerts) => dispatch(addAlerts(alerts))
});
export function GlobalFooter() {
const { t } = useTranslation();
useEffect(() => {
window.Canny("initChangelog", {
appID: "680bd2c7ee501290377f6686",
position: "top",
align: "left",
theme: "light" // options: light [default], dark, auto
});
}, []);
return (
<Footer>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
margin: "1rem 0rem"
}}
>
<Link to="/manage/feature-request">
<Button icon={<BulbOutlined />} type="text">
{t("general.labels.feature-request")}
</Button>
</Link>
<Space>
<WssStatusDisplayComponent />
<div>
{`${InstanceRenderManager({
imex: t("titles.imexonline"),
rome: t("titles.romeonline")
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
</div>
<Button icon={<AlertOutlined />} data-canny-changelog type="text">
{t("general.labels.changelog")}
</Button>
</Space>
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
Disclaimer & Notices
</Link>
</div>
</Footer>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(GlobalFooter);

View File

@@ -12,7 +12,7 @@ import { PageHeader } from "@ant-design/pro-layout";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Dropdown, Input, Space, Table, Tag } from "antd"; import { Button, Dropdown, Input, Space, Table, Tag } from "antd";
import axios from "axios"; import axios from "axios";
import React, { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -32,7 +32,7 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import _ from "lodash"; import _ from "lodash";
import { FaTasks } from "react-icons/fa"; import { FaTasks } from "react-icons/fa";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop, selectPartsManagementOnly } from "../../redux/user/user.selectors";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
@@ -45,11 +45,13 @@ import PartsOrderDrawer from "../parts-order-list-table/parts-order-list-table-d
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container"; import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import JobLinesExpander from "./job-lines-expander.component"; import JobLinesExpander from "./job-lines-expander.component";
import JobLinesPartPriceChange from "./job-lines-part-price-change.component"; import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
import JobLinesExpanderSimple from "./jobs-lines-expander-simple.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
technician: selectTechnician technician: selectTechnician,
partsManagementOnly: selectPartsManagementOnly
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -79,7 +81,7 @@ export function JobLinesComponent({
handleBillOnRowClick, handleBillOnRowClick,
handlePartsOrderOnRowClick, handlePartsOrderOnRowClick,
handlePartsDispatchOnRowClick, handlePartsDispatchOnRowClick,
simple partsManagementOnly
}) { }) {
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK); const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
const { const {
@@ -93,7 +95,13 @@ export function JobLinesComponent({
const [selectedLines, setSelectedLines] = useState([]); const [selectedLines, setSelectedLines] = useState([]);
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
filteredInfo: {} filteredInfo: {
...(partsManagementOnly
? {
part_type: ["PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG"]
}
: {})
}
}); });
const { t } = useTranslation(); const { t } = useTranslation();
const jobIsPrivate = bodyshop.md_ins_cos.find((c) => c.name === job.ins_co_nm)?.private; const jobIsPrivate = bodyshop.md_ins_cos.find((c) => c.name === job.ins_co_nm)?.private;
@@ -221,7 +229,7 @@ export function JobLinesComponent({
sorter: (a, b) => a.part_qty - b.part_qty, sorter: (a, b) => a.part_qty - b.part_qty,
sortOrder: state.sortedInfo.columnKey === "part_qty" && state.sortedInfo.order sortOrder: state.sortedInfo.columnKey === "part_qty" && state.sortedInfo.order
}, },
...(!simple ...(!partsManagementOnly
? [ ? [
{ {
title: t("joblines.fields.mod_lbr_ty"), title: t("joblines.fields.mod_lbr_ty"),
@@ -273,7 +281,7 @@ export function JobLinesComponent({
key: "location", key: "location",
render: (text, record) => <JobLineLocationPopup jobline={record} disabled={jobRO} /> render: (text, record) => <JobLineLocationPopup jobline={record} disabled={jobRO} />
}, },
...(!simple && HasFeatureAccess({ featureName: "bills", bodyshop }) ...(!partsManagementOnly && HasFeatureAccess({ featureName: "bills", bodyshop })
? [ ? [
{ {
title: t("joblines.labels.billref"), title: t("joblines.labels.billref"),
@@ -307,70 +315,74 @@ export function JobLinesComponent({
onFilter: (value, record) => value.includes(record.status), onFilter: (value, record) => value.includes(record.status),
render: (text, record) => <JobLineStatusPopup jobline={record} disabled={jobRO} /> render: (text, record) => <JobLineStatusPopup jobline={record} disabled={jobRO} />
}, },
{ ...!partsManagementOnly
title: t("general.labels.actions"), ? [
dataIndex: "actions", {
key: "actions", title: t("general.labels.actions"),
render: (text, record) => ( dataIndex: "actions",
<Space> key: "actions",
{(record.manual_line || jobIsPrivate) && !technician && ( render: (text, record) => (
<> <Space>
<Button {(record.manual_line || jobIsPrivate) && !technician && (
disabled={jobRO} <>
onClick={() => { <Button
setJobLineEditContext({ disabled={jobRO}
actions: { refetch: refetch, submit: form && form.submit }, onClick={() => {
context: { ...record, jobid: job.id } setJobLineEditContext({
}); actions: { refetch: refetch, submit: form && form.submit },
}} context: { ...record, jobid: job.id }
> });
<EditFilled /> }}
</Button> >
</> <EditFilled />
)} </Button>
<Button </>
title={t("tasks.buttons.create")} )}
onClick={() => { <Button
setTaskUpsertContext({ title={t("tasks.buttons.create")}
context: { onClick={() => {
jobid: job.id, setTaskUpsertContext({
joblineid: record.id context: {
} jobid: job.id,
}); joblineid: record.id
}} }
> });
<FaTasks /> }}
</Button> >
{(record.manual_line || jobIsPrivate) && !technician && ( <FaTasks />
<> </Button>
<Button {(record.manual_line || jobIsPrivate) && !technician && (
disabled={jobRO} <>
onClick={async () => { <Button
await deleteJobLine({ disabled={jobRO}
variables: { joblineId: record.id }, onClick={async () => {
update(cache) { await deleteJobLine({
cache.modify({ variables: { joblineId: record.id },
fields: { update(cache) {
joblines(existingJobLines, { readField }) { cache.modify({
return existingJobLines.filter((jlRef) => record.id !== readField("id", jlRef)); fields: {
joblines(existingJobLines, { readField }) {
return existingJobLines.filter((jlRef) => record.id !== readField("id", jlRef));
}
}
});
} }
} });
}); await axios.post("/job/totalsssu", {
} id: job.id
}); });
await axios.post("/job/totalsssu", { refetch && refetch();
id: job.id }}
}); >
refetch && refetch(); <DeleteFilled />
}} </Button>
> </>
<DeleteFilled /> )}
</Button> </Space>
</> )
)} }
</Space> ]
) : []
}
]; ];
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
@@ -543,17 +555,19 @@ export function JobLinesComponent({
<Dropdown menu={markMenu} trigger={["click"]}> <Dropdown menu={markMenu} trigger={["click"]}>
<Button id="repair-data-mark-button">{t("jobs.actions.mark")}</Button> <Button id="repair-data-mark-button">{t("jobs.actions.mark")}</Button>
</Dropdown> </Dropdown>
<Button {!partsManagementOnly && (
disabled={jobRO || technician} <Button
onClick={() => { disabled={jobRO || technician}
setJobLineEditContext({ onClick={() => {
actions: { refetch: refetch }, setJobLineEditContext({
context: { jobid: job.id } actions: { refetch: refetch },
}); context: { jobid: job.id }
}} });
> }}
{t("joblines.actions.new")} >
</Button> {t("joblines.actions.new")}
</Button>
)}
{InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} disabled={technician} /> })} {InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} disabled={technician} /> })}
<JobCreateIOU job={job} selectedJobLines={selectedLines} /> <JobCreateIOU job={job} selectedJobLines={selectedLines} />
<Input.Search <Input.Search
@@ -577,7 +591,12 @@ export function JobLinesComponent({
x: true x: true
}} }}
expandable={{ expandable={{
expandedRowRender: (record) => <JobLinesExpander jobline={record} jobid={job.id} />, expandedRowRender: (record) =>
partsManagementOnly ? (
<JobLinesExpanderSimple jobline={record} jobid={job.id} />
) : (
<JobLinesExpander jobline={record} jobid={job.id} />
),
rowExpandable: (record) => true, rowExpandable: (record) => true,
//expandRowByClick: true, //expandRowByClick: true,
expandIcon: ({ expanded, onExpand, record }) => expandIcon: ({ expanded, onExpand, record }) =>

View File

@@ -0,0 +1,83 @@
import { useQuery } from "@apollo/client";
import { Col, Row, Skeleton, Timeline, Typography } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors.js";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpanderSimple);
export function JobLinesExpanderSimple({ jobline, jobid, bodyshop, technician }) {
const { t } = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
joblineid: jobline.id
}
});
if (loading) return <Skeleton />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Row>
<Col span={24}>
<Typography.Title level={4}>{t("parts_orders.labels.parts_orders")}</Typography.Title>
<Timeline
items={
data.parts_order_lines.length > 0
? data.parts_order_lines.map((line) => ({
key: line.id,
children: (
<Row wrap>
<Col span={4}>
{!technician ? (
<>
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
{line.parts_order.order_number}
</Link>
</>
) : (
`${line.parts_order.order_number}`
)}
</Col>
<Col span={4}>
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
</Col>
<Col span={4}>{line.parts_order.vendor.name}</Col>
{line.backordered_eta ? (
<Col span={4}>
<span>
{`${t("parts_orders.fields.backordered_eta")}: `}
<DateFormatter>{line.backordered_eta}</DateFormatter>
</span>
</Col>
) : null}
</Row>
)
}))
: [
{
key: "no-orders",
children: t("parts_orders.labels.notyetordered")
}
]
}
/>
</Col>
</Row>
);
}

View File

@@ -11,7 +11,7 @@ import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions.js"; import { insertAuditTrail } from "../../redux/application/application.actions.js";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop, selectPartsManagementOnly } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings.js"; import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter, DateTimeFormatterFunction } from "../../utils/DateFormatter"; import { DateTimeFormatter, DateTimeFormatterFunction } from "../../utils/DateFormatter";
@@ -30,7 +30,8 @@ import "./jobs-detail-header.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
bodyshop: selectBodyshop bodyshop: selectBodyshop,
partsManagementOnly: selectPartsManagementOnly
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -51,19 +52,20 @@ const mapDispatchToProps = (dispatch) => ({
) )
}); });
const colSpan = { export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, partsManagementOnly }) {
xs: { span: 24 },
sm: { span: 24 },
md: { span: 12 },
lg: { span: 6 },
xl: { span: 6 }
};
export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { notification } = useNotification(); const { notification } = useNotification();
const [notesClamped, setNotesClamped] = useState(true); const [notesClamped, setNotesClamped] = useState(true);
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const colSpan = {
xs: { span: 24 },
sm: { span: 24 },
md: { span: partsManagementOnly ? 8 : 12 },
lg: { span: partsManagementOnly ? 8 : 6 },
xl: { span: partsManagementOnly ? 8 : 6 }
};
const vehicleTitle = const vehicleTitle =
`${job.v_model_yr || ""} ${job.v_color || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim(); `${job.v_model_yr || ""} ${job.v_color || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim();
const bodyHrs = job.joblines.filter((j) => j.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0); const bodyHrs = job.joblines.filter((j) => j.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0);
@@ -143,81 +145,86 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail })
<span style={{ margin: "0rem .5rem" }}>/</span> <span style={{ margin: "0rem .5rem" }}>/</span>
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter> <CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
</DataLabel> </DataLabel>
<DataLabel label={t("jobs.fields.alt_transport")}>
{job.alt_transport} {!partsManagementOnly && (
<JobAltTransportChange job={job} /> <>
</DataLabel> <DataLabel label={t("jobs.fields.alt_transport")}>
{job?.cccontracts?.length > 0 && ( {job.alt_transport}
<DataLabel label={t("jobs.labels.contracts")}> <JobAltTransportChange job={job} />
{job.cccontracts.map((c, index) => ( </DataLabel>
<Space key={c.id} wrap> {job?.cccontracts?.length > 0 && (
<Link to={`/manage/courtesycars/contracts/${c.id}`}> <DataLabel label={t("jobs.labels.contracts")}>
{`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`} {job.cccontracts.map((c, index) => (
{index !== job.cccontracts.length - 1 ? "," : null} <Space key={c.id} wrap>
</Link> <Link to={`/manage/courtesycars/contracts/${c.id}`}>
{`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`}
{index !== job.cccontracts.length - 1 ? "," : null}
</Link>
</Space>
))}
</DataLabel>
)}
<DataLabel label={t("jobs.fields.production_vars.note")}>
<ProductionListColumnProductionNote record={job} />
</DataLabel>
<DataLabel label={t("jobs.fields.estimate_sent_approval")}>
<Space>
<Checkbox
checked={!!job.estimate_sent_approval}
onChange={(e) => handleCheckboxChange("estimate_sent_approval", e.target.checked)}
disabled={disabled}
>
{job.estimate_sent_approval && (
<span style={{ color: "#888" }}>
<DateTimeFormatter>{job.estimate_sent_approval}</DateTimeFormatter>
</span>
)}
</Checkbox>
</Space> </Space>
))} </DataLabel>
</DataLabel> <DataLabel label={t("jobs.fields.estimate_approved")}>
<Space>
<Checkbox
checked={!!job.estimate_approved}
onChange={(e) => handleCheckboxChange("estimate_approved", e.target.checked)}
disabled={disabled}
>
{job.estimate_approved && (
<span style={{ color: "#888" }}>
<DateTimeFormatter>{job.estimate_approved}</DateTimeFormatter>
</span>
)}
</Checkbox>
</Space>
</DataLabel>
<Space wrap>
{job.special_coverage_policy && (
<Tag color="tomato">
<Space>
<WarningFilled />
<span>{t("jobs.labels.specialcoveragepolicy")}</span>
</Space>
</Tag>
)}
{job.ca_gst_registrant && (
<Tag color="geekblue">
<Space>
<WarningFilled />
<span>{t("jobs.fields.ca_gst_registrant")}</span>
</Space>
</Tag>
)}
{job.hit_and_run && (
<Tag color="green">
<Space>
<WarningFilled />
<span>{t("jobs.fields.hit_and_run")}</span>
</Space>
</Tag>
)}
</Space>
</>
)} )}
<DataLabel label={t("jobs.fields.production_vars.note")}>
<ProductionListColumnProductionNote record={job} />
</DataLabel>
<DataLabel label={t("jobs.fields.estimate_sent_approval")}>
<Space>
<Checkbox
checked={!!job.estimate_sent_approval}
onChange={(e) => handleCheckboxChange("estimate_sent_approval", e.target.checked)}
disabled={disabled}
>
{job.estimate_sent_approval && (
<span style={{ color: "#888" }}>
<DateTimeFormatter>{job.estimate_sent_approval}</DateTimeFormatter>
</span>
)}
</Checkbox>
</Space>
</DataLabel>
<DataLabel label={t("jobs.fields.estimate_approved")}>
<Space>
<Checkbox
checked={!!job.estimate_approved}
onChange={(e) => handleCheckboxChange("estimate_approved", e.target.checked)}
disabled={disabled}
>
{job.estimate_approved && (
<span style={{ color: "#888" }}>
<DateTimeFormatter>{job.estimate_approved}</DateTimeFormatter>
</span>
)}
</Checkbox>
</Space>
</DataLabel>
<Space wrap>
{job.special_coverage_policy && (
<Tag color="tomato">
<Space>
<WarningFilled />
<span>{t("jobs.labels.specialcoveragepolicy")}</span>
</Space>
</Tag>
)}
{job.ca_gst_registrant && (
<Tag color="geekblue">
<Space>
<WarningFilled />
<span>{t("jobs.fields.ca_gst_registrant")}</span>
</Space>
</Tag>
)}
{job.hit_and_run && (
<Tag color="green">
<Space>
<WarningFilled />
<span>{t("jobs.fields.hit_and_run")}</span>
</Space>
</Tag>
)}
</Space>
</div> </div>
</Card> </Card>
</Col> </Col>
@@ -334,17 +341,19 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail })
</div> </div>
</Card> </Card>
</Col> </Col>
<Col {...colSpan}> {!partsManagementOnly && (
<Card style={{ height: "100%" }} title={t("jobs.labels.employeeassignments")}> <Col {...colSpan}>
<div> <Card style={{ height: "100%" }} title={t("jobs.labels.employeeassignments")}>
<JobEmployeeAssignments job={job} /> <div>
<Divider style={{ margin: ".5rem" }} /> <JobEmployeeAssignments job={job} />
<DataLabel label={t("jobs.labels.labor_hrs")}> <Divider style={{ margin: ".5rem" }} />
{bodyHrs.toFixed(1)} / {refinishHrs.toFixed(1)} / {(bodyHrs + refinishHrs).toFixed(1)} <DataLabel label={t("jobs.labels.labor_hrs")}>
</DataLabel> {bodyHrs.toFixed(1)} / {refinishHrs.toFixed(1)} / {(bodyHrs + refinishHrs).toFixed(1)}
</div> </DataLabel>
</Card> </div>
</Col> </Card>
</Col>
)}
</Row> </Row>
); );
} }

View File

@@ -8,27 +8,27 @@ import PartsDispatchTable from "../parts-dispatch-table/parts-dispatch-table.com
import PartsOrderListTableComponent from "../parts-order-list-table/parts-order-list-table.component"; import PartsOrderListTableComponent from "../parts-order-list-table/parts-order-list-table.component";
import PartsOrderModal from "../parts-order-modal/parts-order-modal.container"; import PartsOrderModal from "../parts-order-modal/parts-order-modal.container";
export default function JobsDetailPliComponent({ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectPartsManagementOnly } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
partsManagementOnly: selectPartsManagementOnly
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPliComponent);
export function JobsDetailPliComponent({
job, job,
billsQuery, billsQuery,
handleBillOnRowClick, handleBillOnRowClick,
handlePartsOrderOnRowClick, handlePartsOrderOnRowClick,
handlePartsDispatchOnRowClick handlePartsDispatchOnRowClick,
partsManagementOnly
}) { }) {
return ( if (partsManagementOnly) {
<div> return (
<PartsOrderModal />
{billsQuery.error ? <AlertComponent message={billsQuery.error.message} type="error" /> : null}
<BillDetailEditcontainer />
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}>
<JobBillsTotal
bills={billsQuery.data ? billsQuery.data.bills : []}
partsOrders={billsQuery.data ? billsQuery.data.parts_orders : []}
loading={billsQuery.loading}
jobTotals={job.job_totals}
/>
</Col>
<Col span={24}> <Col span={24}>
<PartsOrderListTableComponent <PartsOrderListTableComponent
job={job} job={job}
@@ -36,13 +36,38 @@ export default function JobsDetailPliComponent({
billsQuery={billsQuery} billsQuery={billsQuery}
/> />
</Col> </Col>
<Col span={24}>
<BillsListTable job={job} handleOnRowClick={handleBillOnRowClick} billsQuery={billsQuery} />
</Col>
<Col span={24}>
<PartsDispatchTable job={job} handleOnRowClick={handlePartsDispatchOnRowClick} billsQuery={billsQuery} />
</Col>
</Row> </Row>
</div> );
); } else {
return (
<div>
<PartsOrderModal />
{billsQuery.error ? <AlertComponent message={billsQuery.error.message} type="error" /> : null}
<BillDetailEditcontainer />
<Row gutter={[16, 16]}>
<Col span={24}>
<JobBillsTotal
bills={billsQuery.data ? billsQuery.data.bills : []}
partsOrders={billsQuery.data ? billsQuery.data.parts_orders : []}
loading={billsQuery.loading}
jobTotals={job.job_totals}
/>
</Col>
<Col span={24}>
<PartsOrderListTableComponent
job={job}
handleOnRowClick={handlePartsOrderOnRowClick}
billsQuery={billsQuery}
/>
</Col>
<Col span={24}>
<BillsListTable job={job} handleOnRowClick={handleBillOnRowClick} billsQuery={billsQuery} />
</Col>
<Col span={24}>
<PartsDispatchTable job={job} handleOnRowClick={handlePartsDispatchOnRowClick} billsQuery={billsQuery} />
</Col>
</Row>
</div>
);
}
} }

View File

@@ -14,7 +14,7 @@ import { QUERY_BILL_BY_PK } from "../../graphql/bills.queries";
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries"; import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop, selectPartsManagementOnly } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
@@ -31,7 +31,8 @@ import PrintWrapper from "../print-wrapper/print-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
bodyshop: selectBodyshop bodyshop: selectBodyshop,
partsManagementOnly: selectPartsManagementOnly
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -60,7 +61,8 @@ export function PartsOrderListTableDrawerComponent({
billsQuery, billsQuery,
handleOnRowClick, handleOnRowClick,
setPartsReceiveContext, setPartsReceiveContext,
setTaskUpsertContext setTaskUpsertContext,
partsManagementOnly
}) { }) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1]) .filter((screen) => !!screen[1])
@@ -134,19 +136,21 @@ export function PartsOrderListTableDrawerComponent({
> >
{t("parts_orders.actions.receive")} {t("parts_orders.actions.receive")}
</Button> </Button>
<Button {!partsManagementOnly && (
title={t("tasks.buttons.create")} <Button
onClick={() => { title={t("tasks.buttons.create")}
setTaskUpsertContext({ onClick={() => {
context: { setTaskUpsertContext({
jobid: job.id, context: {
partsorderid: record.id jobid: job.id,
} partsorderid: record.id
}); }
}} });
> }}
<FaTasks /> >
</Button> <FaTasks />
</Button>
)}
<Popconfirm <Popconfirm
title={t("parts_orders.labels.confirmdelete")} title={t("parts_orders.labels.confirmdelete")}
disabled={jobRO} disabled={jobRO}
@@ -170,43 +174,44 @@ export function PartsOrderListTableDrawerComponent({
<DeleteFilled /> <DeleteFilled />
</Button> </Button>
</Popconfirm> </Popconfirm>
{!partsManagementOnly && (
<Button <Button
disabled={ disabled={
(jobRO ? !record.return : jobRO) || (jobRO ? !record.return : jobRO) ||
record.vendor.id === bodyshop.inhousevendorid || record.vendor.id === bodyshop.inhousevendorid ||
!HasFeatureAccess({ bodyshop, featureName: "bills" }) !HasFeatureAccess({ bodyshop, featureName: "bills" })
} }
onClick={() => { onClick={() => {
logImEXEvent("parts_order_receive_bill"); logImEXEvent("parts_order_receive_bill");
setBillEnterContext({ setBillEnterContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
context: { context: {
job: job, job: job,
bill: { bill: {
vendorid: record.vendor.id, vendorid: record.vendor.id,
is_credit_memo: record.return, is_credit_memo: record.return,
billlines: record.parts_order_lines.map((pol) => ({ billlines: record.parts_order_lines.map((pol) => ({
joblineid: pol.job_line_id || "noline", joblineid: pol.job_line_id || "noline",
line_desc: pol.line_desc, line_desc: pol.line_desc,
quantity: pol.quantity, quantity: pol.quantity,
actual_price: pol.act_price, actual_price: pol.act_price,
cost_center: pol.jobline?.part_type cost_center: pol.jobline?.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? pol.jobline.part_type !== "PAE" ? pol.jobline.part_type !== "PAE"
? pol.jobline.part_type ? pol.jobline.part_type
: null : null
: responsibilityCenters.defaults && : responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null) (responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
: null : null
})) }))
}
} }
} });
}); }}
}} >
> <LockWrapperComponent featureName="bills">{t("parts_orders.actions.receivebill")}</LockWrapperComponent>
<LockWrapperComponent featureName="bills">{t("parts_orders.actions.receivebill")}</LockWrapperComponent> </Button>
</Button> )}
<PrintWrapper <PrintWrapper
templateObject={{ templateObject={{

View File

@@ -10,7 +10,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries"; import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop, selectPartsManagementOnly } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
@@ -23,7 +23,8 @@ import ShareToTeamsButton from "../share-to-teams/share-to-teams.component.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
bodyshop: selectBodyshop bodyshop: selectBodyshop,
partsManagementOnly: selectPartsManagementOnly
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -52,7 +53,8 @@ export function PartsOrderListTableComponent({
billsQuery, billsQuery,
handleOnRowClick, handleOnRowClick,
setPartsReceiveContext, setPartsReceiveContext,
setTaskUpsertContext setTaskUpsertContext,
partsManagementOnly
}) { }) {
const responsibilityCenters = bodyshop.md_responsibility_centers; const responsibilityCenters = bodyshop.md_responsibility_centers;
const Templates = TemplateList("partsorder", { job }); const Templates = TemplateList("partsorder", { job });
@@ -106,18 +108,23 @@ export function PartsOrderListTableComponent({
> >
{t("parts_orders.actions.receive")} {t("parts_orders.actions.receive")}
</Button> </Button>
<Button {!partsManagementOnly && (
title={t("tasks.buttons.create")} <>
icon={<FaTasks />} <Button
onClick={() => { title={t("tasks.buttons.create")}
setTaskUpsertContext({ icon={<FaTasks />}
context: { onClick={() => {
jobid: job.id, setTaskUpsertContext({
partsorderid: record.id context: {
} jobid: job.id,
}); partsorderid: record.id
}} }
/> });
}}
/>
</>
)}
<Popconfirm <Popconfirm
title={t("parts_orders.labels.confirmdelete")} title={t("parts_orders.labels.confirmdelete")}
disabled={jobRO} disabled={jobRO}
@@ -141,47 +148,49 @@ export function PartsOrderListTableComponent({
<Button disabled={jobRO} icon={<DeleteFilled />} /> <Button disabled={jobRO} icon={<DeleteFilled />} />
</Popconfirm> </Popconfirm>
<Button {!partsManagementOnly && (
disabled={ <Button
(jobRO ? !record.return : jobRO) || disabled={
record.vendor.id === bodyshop.inhousevendorid || (jobRO ? !record.return : jobRO) ||
!HasFeatureAccess({ bodyshop, featureName: "bills" }) record.vendor.id === bodyshop.inhousevendorid ||
} !HasFeatureAccess({ bodyshop, featureName: "bills" })
onClick={() => { }
logImEXEvent("parts_order_receive_bill"); onClick={() => {
logImEXEvent("parts_order_receive_bill");
setBillEnterContext({ setBillEnterContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
context: { context: {
job: job, job: job,
bill: { bill: {
vendorid: record.vendor.id, vendorid: record.vendor.id,
is_credit_memo: record.return, is_credit_memo: record.return,
billlines: record.parts_order_lines.map((pol) => { billlines: record.parts_order_lines.map((pol) => {
return { return {
joblineid: pol.job_line_id || "noline", joblineid: pol.job_line_id || "noline",
line_desc: pol.line_desc, line_desc: pol.line_desc,
quantity: pol.quantity, quantity: pol.quantity,
actual_price: pol.act_price, actual_price: pol.act_price,
cost_center: pol.jobline?.part_type cost_center: pol.jobline?.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? pol.jobline.part_type !== "PAE" ? pol.jobline.part_type !== "PAE"
? pol.jobline.part_type ? pol.jobline.part_type
: null : null
: responsibilityCenters.defaults && : responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null) (responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
: null : null
}; };
}) })
}
} }
} });
}); }}
}} >
> <LockWrapperComponent featureName="bills">{t("parts_orders.actions.receivebill")}</LockWrapperComponent>
<LockWrapperComponent featureName="bills">{t("parts_orders.actions.receivebill")}</LockWrapperComponent> </Button>
</Button> )}
<PrintWrapper <PrintWrapper
templateObject={{ templateObject={{

View File

@@ -1865,6 +1865,7 @@ export const QUERY_SIMPLIFIED_PARTS_PAGINATED_STATUS_FILTERED = gql`
owner_owing owner_owing
ro_number ro_number
po_number po_number
converted
status status
updated_at updated_at
ded_amt ded_amt

View File

@@ -1,11 +1,10 @@
import { Button, FloatButton, Layout, Space, Spin } from "antd"; import { FloatButton, Layout, Spin } from "antd";
import { AlertOutlined, BulbOutlined } from "@ant-design/icons";
// import preval from "preval.macro"; // import preval from "preval.macro";
import React, { lazy, Suspense, useEffect, useState } from "react"; import { lazy, Suspense, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component"; import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
import ChatAffixContainer from "../../components/chat-affix/chat-affix.container"; import ChatAffixContainer from "../../components/chat-affix/chat-affix.container";
@@ -15,19 +14,19 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
//Component Imports //Component Imports
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import TestComponent from "../../components/_test/test.page"; import TestComponent from "../../components/_test/test.page";
import GlobalFooter from "../../components/global-footer/global-footer.component.jsx";
import HeaderContainer from "../../components/header/header.container"; import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component"; import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
import UpdateAlert from "../../components/update-alert/update-alert.component"; import UpdateAlert from "../../components/update-alert/update-alert.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import WssStatusDisplayComponent from "../../components/wss-status-display/wss-status-display.component.jsx";
import { selectAlerts } from "../../redux/application/application.selectors.js";
import { addAlerts } from "../../redux/application/application.actions.js";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { useSocket } from "../../contexts/SocketIO/useSocket.js"; import { useSocket } from "../../contexts/SocketIO/useSocket.js";
import { addAlerts } from "../../redux/application/application.actions.js";
import { selectAlerts } from "../../redux/application/application.selectors.js";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
const JobsPage = lazy(() => import("../jobs/jobs.page")); const JobsPage = lazy(() => import("../jobs/jobs.page"));
@@ -658,38 +657,7 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
<FloatButton.BackTop style={{ right: 100, bottom: 25 }} /> <FloatButton.BackTop style={{ right: 100, bottom: 25 }} />
</Content> </Content>
<Footer> <GlobalFooter />
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
margin: "1rem 0rem"
}}
>
<Link to="/manage/feature-request">
<Button icon={<BulbOutlined />} type="text">
{t("general.labels.feature-request")}
</Button>
</Link>
<Space>
<WssStatusDisplayComponent />
<div onClick={broadcastMessage}>
{`${InstanceRenderManager({
imex: t("titles.imexonline"),
rome: t("titles.romeonline")
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
</div>
<Button icon={<AlertOutlined />} data-canny-changelog type="text">
{t("general.labels.changelog")}
</Button>
</Space>
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
Disclaimer & Notices
</Link>
</div>
</Footer>
</Layout> </Layout>
</> </>
); );

View File

@@ -22,11 +22,12 @@ import { QUERY_PARTS_BILLS_BY_JOBID } from "../../graphql/bills.queries.js";
import { insertAuditTrail } from "../../redux/application/application.actions.js"; import { insertAuditTrail } from "../../redux/application/application.actions.js";
import { selectJobReadOnly } from "../../redux/application/application.selectors.js"; import { selectJobReadOnly } from "../../redux/application/application.selectors.js";
import { setModalContext } from "../../redux/modals/modals.actions.js"; import { setModalContext } from "../../redux/modals/modals.actions.js";
import { selectBodyshop } from "../../redux/user/user.selectors.js"; import { selectBodyshop, selectPartsManagementOnly } from "../../redux/user/user.selectors.js";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
jobRO: selectJobReadOnly jobRO: selectJobReadOnly,
partsManagementOnly: selectPartsManagementOnly
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) => setPrintCenterContext: (context) =>
@@ -53,7 +54,8 @@ export function SimplifiedPartsJobDetailComponent({
job, job,
mutationUpdateJob, mutationUpdateJob,
insertAuditTrail, insertAuditTrail,
refetch refetch,
partsManagementOnly
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
@@ -141,10 +143,14 @@ export function SimplifiedPartsJobDetailComponent({
{t("jobs.actions.printCenter")} {t("jobs.actions.printCenter")}
</Button> </Button>
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch} /> {!partsManagementOnly && (
<Button type="primary" loading={loading} disabled={jobRO} onClick={() => form.submit()}> <>
{t("general.actions.save")} <JobsDetailHeaderActions key="actions" job={job} refetch={refetch} />
</Button> <Button type="primary" loading={loading} disabled={jobRO} onClick={() => form.submit()}>
{t("general.actions.save")}
</Button>
</>
)}
</Space> </Space>
); );
@@ -153,7 +159,7 @@ export function SimplifiedPartsJobDetailComponent({
<JobLineUpsertModalContainer /> <JobLineUpsertModalContainer />
<PageHeader title={<Space>{job.ro_number || t("general.labels.na")}</Space>} extra={menuExtra} /> <PageHeader title={<Space>{job.ro_number || t("general.labels.na")}</Space>} extra={menuExtra} />
<JobsDetailHeader job={job} /> <JobsDetailHeader job={job} disabled={true} />
<Divider type="horizontal" /> <Divider type="horizontal" />
<JobProfileDataWarning job={job} /> <JobProfileDataWarning job={job} />
<FormFieldsChanged form={form} /> <FormFieldsChanged form={form} />
@@ -178,7 +184,6 @@ export function SimplifiedPartsJobDetailComponent({
handlePartsDispatchOnRowClick={handlePartsDispatchOnRowClick} handlePartsDispatchOnRowClick={handlePartsDispatchOnRowClick}
refetch={refetch} refetch={refetch}
form={form} form={form}
simple
/> />
) )
}, },

View File

@@ -63,7 +63,7 @@ function SimplifiedPartsJobsDetailContainer({ setBreadcrumbs, addRecentItem, set
ro_number: (data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na") ro_number: (data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")
}); });
setBreadcrumbs([ setBreadcrumbs([
{ link: "/parts/jobs", label: t("titles.bc.jobs") }, { link: "/parts/", label: t("titles.bc.jobs") },
{ {
link: `/parts/jobs/${jobId}`, link: `/parts/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", { label: t("titles.bc.jobs-detail", {
@@ -81,7 +81,7 @@ function SimplifiedPartsJobsDetailContainer({ setBreadcrumbs, addRecentItem, set
"job", "job",
`${data.jobs_by_pk.ro_number || t("general.labels.na")} | ${OwnerNameDisplayFunction(data.jobs_by_pk)}`, `${data.jobs_by_pk.ro_number || t("general.labels.na")} | ${OwnerNameDisplayFunction(data.jobs_by_pk)}`,
`/manage/jobs/${jobId}` `/parts/jobs/${jobId}`
) )
); );
} }

View File

@@ -1,26 +1,24 @@
import { AlertOutlined, BulbOutlined } from "@ant-design/icons";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import { Button, FloatButton, Layout, Space, Spin } from "antd"; import { FloatButton, Layout, Spin } from "antd";
import { lazy, Suspense, useEffect, useState } from "react"; import { lazy, Suspense, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component.jsx"; import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component.jsx";
import ConflictComponent from "../../components/conflict/conflict.component.jsx"; import ConflictComponent from "../../components/conflict/conflict.component.jsx";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component.jsx"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component.jsx";
import GlobalFooter from "../../components/global-footer/global-footer.component.jsx";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component.jsx"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component.jsx";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container.jsx"; import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container.jsx";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component.jsx"; import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component.jsx";
import UpdateAlert from "../../components/update-alert/update-alert.component.jsx"; import UpdateAlert from "../../components/update-alert/update-alert.component.jsx";
import WssStatusDisplayComponent from "../../components/wss-status-display/wss-status-display.component.jsx";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { useSocket } from "../../contexts/SocketIO/useSocket.js"; import { useSocket } from "../../contexts/SocketIO/useSocket.js";
import { addAlerts } from "../../redux/application/application.actions.js"; import { addAlerts } from "../../redux/application/application.actions.js";
import { selectAlerts } from "../../redux/application/application.selectors.js"; import { selectAlerts } from "../../redux/application/application.selectors.js";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors.js"; import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors.js";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js"; import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
const SimplifiedPartsJobsPage = lazy(() => import("../simplified-parts-jobs/simplified-parts-jobs.page.jsx")); const SimplifiedPartsJobsPage = lazy(() => import("../simplified-parts-jobs/simplified-parts-jobs.page.jsx"));
const SimplifiedPartsJobsDetailPage = lazy( const SimplifiedPartsJobsDetailPage = lazy(
() => import("../simplified-parts-jobs-detail/simplified-parts-jobs-detail.container.jsx") () => import("../simplified-parts-jobs-detail/simplified-parts-jobs-detail.container.jsx")
@@ -29,12 +27,10 @@ const ShopPage = lazy(() => import("../shop/shop.page.component.jsx"));
const ShopVendorPageContainer = lazy(() => import("../shop-vendor/shop-vendor.page.container.jsx")); const ShopVendorPageContainer = lazy(() => import("../shop-vendor/shop-vendor.page.container.jsx"));
const EmailOverlayContainer = lazy(() => import("../../components/email-overlay/email-overlay.container.jsx")); const EmailOverlayContainer = lazy(() => import("../../components/email-overlay/email-overlay.container.jsx"));
const FeatureRequestPage = lazy(() => import("../feature-request/feature-request.page.jsx")); const FeatureRequestPage = lazy(() => import("../feature-request/feature-request.page.jsx"));
const JobCostingModal = lazy(() => import("../../components/job-costing-modal/job-costing-modal.container.jsx"));
const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container.jsx")); const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container.jsx"));
const BillEnterModalContainer = lazy(() => import("../../components/bill-enter-modal/bill-enter-modal.container.jsx"));
const Help = lazy(() => import("../help/help.page.jsx")); const Help = lazy(() => import("../help/help.page.jsx"));
const { Content, Footer } = Layout; const { Content } = Layout;
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict, conflict: selectInstanceConflict,
@@ -141,8 +137,6 @@ export function SimplifiedPartsPage({ conflict, bodyshop, alerts, setAlerts }) {
} }
> >
<BreadCrumbs /> <BreadCrumbs />
<BillEnterModalContainer />
<JobCostingModal />
<ReportCenterModal /> <ReportCenterModal />
<EmailOverlayContainer /> <EmailOverlayContainer />
<PrintCenterModalContainer /> <PrintCenterModalContainer />
@@ -199,13 +193,6 @@ export function SimplifiedPartsPage({ conflict, bodyshop, alerts, setAlerts }) {
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />; else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />;
else PageContent = AppRouteTable; else PageContent = AppRouteTable;
const broadcastMessage = () => {
if (socket && bodyshop && bodyshop.id) {
console.log(`Broadcasting message to bodyshop ${bodyshop.id}:`);
socket.emit("broadcast-to-bodyshop", bodyshop.id, `Hello from ${clientId}`);
}
};
return ( return (
<Layout style={{ minHeight: "100vh" }} className="layout-container"> <Layout style={{ minHeight: "100vh" }} className="layout-container">
<UpdateAlert /> <UpdateAlert />
@@ -216,38 +203,7 @@ export function SimplifiedPartsPage({ conflict, bodyshop, alerts, setAlerts }) {
</Sentry.ErrorBoundary> </Sentry.ErrorBoundary>
<FloatButton.BackTop style={{ right: 100, bottom: 25 }} /> <FloatButton.BackTop style={{ right: 100, bottom: 25 }} />
</Content> </Content>
<Footer> <GlobalFooter />
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
margin: "1rem 0rem"
}}
>
<Link to="/manage/feature-request">
<Button icon={<BulbOutlined />} type="text">
{t("general.labels.feature-request")}
</Button>
</Link>
<Space>
<WssStatusDisplayComponent />
<div onClick={broadcastMessage}>
{`${InstanceRenderManager({
imex: t("titles.imexonline"),
rome: t("titles.romeonline")
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
</div>
<Button icon={<AlertOutlined />} data-canny-changelog type="text">
{t("general.labels.changelog")}
</Button>
</Space>
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
Disclaimer & Notices
</Link>
</div>
</Footer>
</Layout> </Layout>
); );
} }

View File

@@ -123,3 +123,8 @@ export const setImexShopId = (imexshopid) => ({
type: UserActionTypes.SET_IMEX_SHOP_ID, type: UserActionTypes.SET_IMEX_SHOP_ID,
payload: imexshopid payload: imexshopid
}); });
export const setPartsManagementOnly = (partsManagementOnly) => ({
type: UserActionTypes.SET_PARTS_MANAGEMENT_ONLY,
payload: partsManagementOnly
});

View File

@@ -7,6 +7,7 @@ const INITIAL_STATE = {
//language: "en-US" //language: "en-US"
}, },
bodyshop: null, bodyshop: null,
partsManagementOnly: null,
loginLoading: false, loginLoading: false,
fingerprint: null, fingerprint: null,
error: null, error: null,
@@ -125,7 +126,11 @@ const userReducer = (state = INITIAL_STATE, action) => {
...state, ...state,
imexshopid: action.payload imexshopid: action.payload
}; };
case UserActionTypes.SET_PARTS_MANAGEMENT_ONLY:
return {
...state,
partsManagementOnly: action.payload
};
default: default:
return state; return state;
} }

View File

@@ -38,6 +38,7 @@ import {
setInstanceConflict, setInstanceConflict,
setInstanceId, setInstanceId,
setLocalFingerprint, setLocalFingerprint,
setPartsManagementOnly,
signInFailure, signInFailure,
signInSuccess, signInSuccess,
signOutFailure, signOutFailure,
@@ -344,13 +345,13 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
payload.features?.allAccess === true payload.features?.allAccess === true
? window.$crisp.push(["set", "session:segments", [["allAccess"]]]) ? window.$crisp.push(["set", "session:segments", [["allAccess"]]])
: (() => { : (() => {
const featureKeys = Object.keys(payload.features).filter( const featureKeys = Object.keys(payload.features).filter(
(key) => (key) =>
payload.features[key] === true || payload.features[key] === true ||
(typeof payload.features[key] === "string" && !isNaN(Date.parse(payload.features[key]))) (typeof payload.features[key] === "string" && !isNaN(Date.parse(payload.features[key])))
); );
window.$crisp.push(["set", "session:segments", [["basic", ...featureKeys]]]); window.$crisp.push(["set", "session:segments", [["basic", ...featureKeys]]]);
})(); })();
InstanceRenderManager({ InstanceRenderManager({
executeFunction: true, executeFunction: true,
@@ -359,6 +360,10 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname }); window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname });
} }
}); });
//Set whether it is for parts management only.
yield put(setPartsManagementOnly(true || payload.features.partsManagementOnly));
} catch (error) { } catch (error) {
console.warn("Couldnt find $crisp.", error.message); console.warn("Couldnt find $crisp.", error.message);
} }

View File

@@ -1,3 +1,4 @@
import { create } from "lodash";
import { createSelector } from "reselect"; import { createSelector } from "reselect";
const selectUser = (state) => state.user; const selectUser = (state) => state.user;
@@ -17,3 +18,7 @@ export const selectAuthLevel = createSelector([selectUser], (user) => user.authL
export const selectLoginLoading = createSelector([selectUser], (user) => user.loginLoading); export const selectLoginLoading = createSelector([selectUser], (user) => user.loginLoading);
export const selectCurrentEula = createSelector([selectUser], (user) => user.currentEula); export const selectCurrentEula = createSelector([selectUser], (user) => user.currentEula);
export const selectPartsManagementOnly = createSelector(
[selectUser],
(user) => user.partsManagementOnly
);

View File

@@ -33,6 +33,7 @@ const UserActionTypes = {
CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE", CHECK_ACTION_CODE_FAILURE: "CHECK_ACTION_CODE_FAILURE",
SET_CURRENT_EULA: "SET_CURRENT_EULA", SET_CURRENT_EULA: "SET_CURRENT_EULA",
EULA_ACCEPTED: "EULA_ACCEPTED", EULA_ACCEPTED: "EULA_ACCEPTED",
SET_IMEX_SHOP_ID: "SET_IMEX_SHOP_ID" SET_IMEX_SHOP_ID: "SET_IMEX_SHOP_ID",
SET_PARTS_MANAGEMENT_ONLY: "SET_PARTS_MANAGEMENT_ONLY",
}; };
export default UserActionTypes; export default UserActionTypes;