Merge branch 'feature/IO-3255-simplified-part-management' into feature/IO-2776-cdk-fortellis

# Conflicts:
#	client/package-lock.json
#	client/src/components/dms-cdk-makes/dms-cdk-makes.component.jsx
#	client/src/components/dms-customer-selector/dms-customer-selector.component.jsx
#	client/src/pages/dms/dms.container.jsx
#	package-lock.json
#	package.json
This commit is contained in:
Dave
2025-08-21 11:13:27 -04:00
698 changed files with 7509 additions and 3049 deletions

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";

View File

@@ -1,21 +1,18 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import QboAuthorizeComponent from "../../components/qbo-authorize/qbo-authorize.component";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
});
export function AccountingReceivablesContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
export function AccountingReceivablesContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";

View File

@@ -18,11 +18,10 @@ import { alphaSort, dateSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" }))
});
export function BillsListPage({ loading, data, refetch, total, setPartsOrderContext, setBillEnterContext }) {
export function BillsListPage({ loading, data, refetch, total, setBillEnterContext }) {
const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
@@ -193,7 +192,6 @@ export function BillsListPage({ loading, data, refetch, total, setPartsOrderCont
if (search.search && search.search.trim() !== "") {
searchBills();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function searchBills(value) {

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@apollo/client";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";

View File

@@ -1,7 +1,5 @@
import { Button, Col, Form, Row, Space, Switch } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import React from "react";
import { useTranslation } from "react-i18next";
import ContractCarsContainer from "../../components/contract-cars/contract-cars.container";
import ContractFormComponent from "../../components/contract-form/contract-form.component";

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { Card, Form } from "antd";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";

View File

@@ -1,7 +1,5 @@
import { Button, Col, Dropdown, Form, Row, Space, Typography } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import ContractConvertToRo from "../../components/contract-convert-to-ro/contract-convert-to-ro.component";
@@ -37,8 +35,8 @@ export function ContractDetailPage({
</Row>
<PageHeader
title={t("contracts.labels.agreement", {
agreement_num: contract && contract.agreementnumber,
status: t(contract && contract.status)
agreement_num: contract?.agreementnumber,
status: t(contract?.status)
})}
extra={
<Form.Item shouldUpdate>
@@ -78,7 +76,7 @@ export function ContractDetailPage({
{t("general.actions.save")}
</Button>
<Button
disabled={!!!contract || (contract && contract.status !== "contracts.status.out")}
disabled={!contract || (contract && contract.status !== "contracts.status.out")}
onClick={() => {
setCourtesyCarReturnModalContext({
actions: { refetch },

View File

@@ -1,7 +1,7 @@
import { useMutation, useQuery } from "@apollo/client";
import { Card, Form } from "antd";
import dayjs from "../../utils/day";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
@@ -52,7 +52,7 @@ export function ContractDetailPageContainer({ setBreadcrumbs, addRecentItem, set
rome: t("titles.romeonline")
})
: t("titles.contracts-detail", {
id: (data && data.cccontracts_by_pk && data.cccontracts_by_pk.agreementnumber) || ""
id: (data?.cccontracts_by_pk && data.cccontracts_by_pk.agreementnumber) || ""
});
setBreadcrumbs([
@@ -64,12 +64,12 @@ export function ContractDetailPageContainer({ setBreadcrumbs, addRecentItem, set
{
link: "/manage/courtesycars/contracts/new",
label: t("titles.bc.contracts-detail", {
number: (data && data.cccontracts_by_pk && data.cccontracts_by_pk.agreementnumber) || ""
number: (data?.cccontracts_by_pk && data.cccontracts_by_pk.agreementnumber) || ""
})
}
]);
if (data && data.cccontracts_by_pk)
if (data?.cccontracts_by_pk)
addRecentItem(
CreateRecentItem(
contractId,
@@ -85,7 +85,7 @@ export function ContractDetailPageContainer({ setBreadcrumbs, addRecentItem, set
const result = await updateContract({
variables: { cccontract: { ...values }, contractId: contractId }
});
if (!!result.errors) {
if (result.errors) {
notification["error"]({
message: t("contracts.errors.saving", {
message: JSON.stringify(result.errors)
@@ -102,7 +102,7 @@ export function ContractDetailPageContainer({ setBreadcrumbs, addRecentItem, set
};
useEffect(() => {
if (data && data.cccontracts_by_pk) form.resetFields();
if (data?.cccontracts_by_pk) form.resetFields();
}, [data, form]);
if (error) return <AlertComponent message={error.message} type="error" />;

View File

@@ -1,4 +1,3 @@
import React from "react";
import ContractsList from "../../components/contracts-list/contracts-list.component";
export default function ContractsPageComponent({ loading, data, refetch, total }) {

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@apollo/client";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";

View File

@@ -1,6 +1,6 @@
import { useMutation } from "@apollo/client";
import { Card, Form } from "antd";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useNavigate } from "react-router-dom";
@@ -37,7 +37,7 @@ export function CourtesyCarCreateContainer({ bodyshop, setBreadcrumbs, setSelect
variables: { courtesycar: { ...values, bodyshopid: bodyshop.id } }
});
if (!!result.errors) {
if (result.errors) {
notification["error"]({
message: t("courtesycars.errors.saving", {
message: JSON.stringify(result.errors)

View File

@@ -1,4 +1,3 @@
import React from "react";
import CourtesyCarCreateFormComponent from "../../components/courtesy-car-form/courtesy-car-form.component";
import CourtesyCarContractListComponent from "../../components/courtesy-car-contract-list/courtesy-car-contract-list.component";
import { Col, Divider, Row } from "antd";

View File

@@ -1,7 +1,7 @@
import { useMutation, useQuery } from "@apollo/client";
import { Form } from "antd";
import dayjs from "../../utils/day";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation, useParams } from "react-router-dom";
@@ -68,19 +68,19 @@ export function CourtesyCarDetailPageContainer({ setBreadcrumbs, addRecentItem,
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
}),
id: (data && data.courtesycars_by_pk && data.courtesycars_by_pk.fleet_number) || ""
id: (data?.courtesycars_by_pk && data.courtesycars_by_pk.fleet_number) || ""
});
setBreadcrumbs([
{ link: "/manage/courtesycars", label: t("titles.bc.courtesycars") },
{
link: `/manage/courtesycars/${(data && data.courtesycars_by_pk && data.courtesycars_by_pk.id) || ""}`,
link: `/manage/courtesycars/${(data?.courtesycars_by_pk && data.courtesycars_by_pk.id) || ""}`,
label: t("titles.bc.courtesycars-detail", {
number: (data && data.courtesycars_by_pk && data.courtesycars_by_pk.fleetnumber) || ""
number: (data?.courtesycars_by_pk && data.courtesycars_by_pk.fleetnumber) || ""
})
}
]);
if (data && data.courtesycars_by_pk)
if (data?.courtesycars_by_pk)
addRecentItem(
CreateRecentItem(
ccId,
@@ -103,7 +103,7 @@ export function CourtesyCarDetailPageContainer({ setBreadcrumbs, addRecentItem,
awaitRefetchQueries: true
});
if (!!result.errors) {
if (result.errors) {
notification["error"]({
message: t("courtesycars.errors.saving", { error: error })
});
@@ -117,7 +117,7 @@ export function CourtesyCarDetailPageContainer({ setBreadcrumbs, addRecentItem,
};
useEffect(() => {
if (data && data.courtesycars_by_pk) {
if (data?.courtesycars_by_pk) {
form.resetFields();
form.resetFields();
}
@@ -126,7 +126,7 @@ export function CourtesyCarDetailPageContainer({ setBreadcrumbs, addRecentItem,
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!data.courtesycars_by_pk) return <NotFound />;
if (!data.courtesycars_by_pk) return <NotFound />;
return (
<RbacWrapper action="courtesycar:detail">

View File

@@ -1,4 +1,3 @@
import React from "react";
import CourtesyCarsListComponent from "../../components/courtesy-cars-list/courtesy-cars-list.component";
export default function CourtesyCarsPageComponent({ loading, data, refetch }) {

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import AlertComponent from "../../components/alert/alert.component";
@@ -45,7 +45,7 @@ export function CourtesyCarsPageContainer({ setBreadcrumbs, setSelectedHeader })
}
>
<RbacWrapper action="courtesycar:list">
<CourtesyCarsPageComponent loading={loading} data={(data && data.courtesycars) || []} refetch={refetch} />
<CourtesyCarsPageComponent loading={loading} data={data?.courtesycars || []} refetch={refetch} />
</RbacWrapper>
</FeatureWrapperComponent>
);

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import DashboardGridComponent from "../../components/dashboard-grid/dashboard-grid.component";

View File

@@ -1,4 +1,3 @@
import React from "react";
import { Typography } from "antd";
import InstanceRenderMgr from "../../utils/instanceRenderMgr";
import { useTranslation } from "react-i18next";

View File

@@ -1,5 +1,5 @@
import { Button, Card, Col, Row, Select, Space } from "antd";
import React, { useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";
@@ -9,13 +9,10 @@ import DmsAllocationsSummaryApComponent from "../../components/dms-allocations-s
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
import { auth } from "../../firebase/firebase.utils";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -33,7 +30,7 @@ export const socket = SocketIO(import.meta.env.PROD ? import.meta.env.VITE_APP_A
}
});
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
export function DmsContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const [logLevel, setLogLevel] = useState("DEBUG");
const history = useNavigate();
@@ -85,7 +82,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
});
});
socket.on("ap-export-complete", (payload) => {
socket.on("ap-export-complete", () => {
notification.open({
type: "success",
message: t("jobs.labels.dms.apexported")
@@ -97,7 +94,6 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!state?.billids) {

View File

@@ -1,7 +1,7 @@
import { useQuery } from "@apollo/client";
import { Button, Card, Col, Result, Row, Select, Space } from "antd";
import queryString from "query-string";
import React, { useEffect, useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
@@ -157,13 +157,12 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
history("/manage/accounting/receivables");
});
if (socket.disconnected) socket.connect();
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (loading) return <LoadingSpinner />;

View File

@@ -3,23 +3,19 @@ import { useQuery } from "@apollo/client";
import { Button, Card, Checkbox, Input, Space, Table, Typography } from "antd";
import _ from "lodash";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { pageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "./../../utils/sorters";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapStateToProps = createStructuredSelector({});
export function ExportLogsPageComponent({ bodyshop }) {
export function ExportLogsPageComponent() {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search } = searchParams;
const history = useNavigate();
@@ -97,9 +93,7 @@ export function ExportLogsPageComponent({ bodyshop }) {
sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) =>
record.job && (
<Link to={`/manage/jobs/${record.job.id}`}>
{(record.job && record.job.ro_number) || t("general.labels.na")}
</Link>
<Link to={`/manage/jobs/${record.job.id}`}>{record.job?.ro_number || t("general.labels.na")}</Link>
)
},
{
@@ -109,11 +103,7 @@ export function ExportLogsPageComponent({ bodyshop }) {
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
sortOrder: sortcolumn === "invoice_number" && sortorder,
render: (text, record) =>
record.bill && (
<Link to={"/manage/bills?billid=" + (record.bill && record.bill.id)}>
{record.bill && record.bill.invoice_number}
</Link>
)
record.bill && <Link to={"/manage/bills?billid=" + record.bill?.id}>{record.bill?.invoice_number}</Link>
},
{
title: t("payments.fields.paymentnum"),
@@ -123,9 +113,7 @@ export function ExportLogsPageComponent({ bodyshop }) {
sortOrder: sortcolumn === "paymentnum" && sortorder,
render: (text, record) =>
record.payment && (
<Link to={"/manage/payments?search=" + (record.payment && record.payment.paymentnum)}>
{record.payment && record.payment.paymentnum}
</Link>
<Link to={"/manage/payments?search=" + record.payment?.paymentnum}>{record.payment?.paymentnum}</Link>
)
},
{
@@ -197,12 +185,12 @@ export function ExportLogsPageComponent({ bodyshop }) {
pagination={{
position: "top",
pageSize: pageLimit,
current: parseInt(page || 1),
current: parseInt(page || 1, 10),
total: data && data.search_exportlog_aggregate.aggregate.count
}}
columns={columns}
rowKey="id"
dataSource={data && data.search_exportlog}
dataSource={data?.search_exportlog}
style={{ height: "100%" }}
scroll={{ x: true }}
onChange={handleTableChange}

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";

View File

@@ -1,5 +1,5 @@
import axios from "axios";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
@@ -33,6 +33,7 @@ export function FeedbackPage({ setBreadcrumbs, setSelectedHeader }) {
theme: "light" // options: light [default], dark, auto
});
}
RenderCanny();
}, []);

View File

@@ -1,4 +1,3 @@
import React from "react";
import HelpRescue from "../../components/help-rescue/help-rescue.component";
export default function HelpPage() {

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@apollo/client";
import { Card, Col, Result, Row, Space, Typography } from "antd";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
@@ -77,7 +77,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!data.jobs_by_pk) return <NotFound />;
if (!data.jobs_by_pk) return <NotFound />;
if (!data.jobs_by_pk.job_totals)
return <Result title={t("jobs.errors.nofinancial")} extra={<JobCalculateTotals job={data.jobs_by_pk} />} />;

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@apollo/client";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";

View File

@@ -1,7 +1,7 @@
import { Button } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
@@ -63,7 +63,7 @@ export function JobsAvailablePageContainer({ partnerVersion, setBreadcrumbs, set
message={t("general.messages.partnernotrunning", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)",
rome: "$t(titles.romeonline)"
})
})}
/>

View File

@@ -1,7 +1,7 @@
import { useQuery } from "@apollo/client";
import { Card, Col, Row, Typography } from "antd";
import dayjs from "../../utils/day";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
@@ -12,21 +12,17 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_JOB_CHECKLISTS } from "../../graphql/jobs.queries";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component";
import UpsellComponent, { upsellEnum } from "../../components/upsell/upsell.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop
});
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
});
export function JobsChecklistViewContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
export function JobsChecklistViewContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const { jobId } = useParams();
const { loading, error, data } = useQuery(QUERY_JOB_CHECKLISTS, {
@@ -48,7 +44,7 @@ export function JobsChecklistViewContainer({ bodyshop, setBreadcrumbs, setSelect
{
link: `/manage/jobs/${jobId}`,
label: t("titles.bc.jobs-detail", {
number: (data && data.jobs_by_pk && data.jobs_by_pk.ro_number) || ""
number: (data?.jobs_by_pk && data.jobs_by_pk.ro_number) || ""
})
},
{
@@ -87,10 +83,10 @@ export function JobsChecklistViewContainer({ bodyshop, setBreadcrumbs, setSelect
<Row gutter={[16, 16]}>
<Col span={12}>
<Typography.Title level={4}>{t("jobs.labels.intakechecklist")}</Typography.Title>
{data.jobs_by_pk.intakechecklist && data.jobs_by_pk.intakechecklist.form && (
{data.jobs_by_pk.intakechecklist?.form && (
<>
<JobChecklistForm
formItems={data.jobs_by_pk.intakechecklist && data.jobs_by_pk.intakechecklist.form}
formItems={data.jobs_by_pk.intakechecklist?.form}
type="intake"
job={data.jobs_by_pk}
readOnly
@@ -101,10 +97,10 @@ export function JobsChecklistViewContainer({ bodyshop, setBreadcrumbs, setSelect
</Col>
<Col span={12}>
<Typography.Title level={4}>{t("jobs.labels.deliverchecklist")}</Typography.Title>
{data.jobs_by_pk.deliverchecklist && data.jobs_by_pk.deliverchecklist.form && (
{data.jobs_by_pk.deliverchecklist?.form && (
<>
<JobChecklistForm
formItems={data.jobs_by_pk.deliverchecklist && data.jobs_by_pk.deliverchecklist.form}
formItems={data.jobs_by_pk.deliverchecklist?.form}
type="deliver"
job={data.jobs_by_pk}
readOnly

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@apollo/client";
import { Result } from "antd";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
@@ -30,7 +30,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader, setJobRe
});
const { t } = useTranslation();
useEffect(() => {
if (data && data.jobs_by_pk) {
if (data?.jobs_by_pk) {
setJobReadOnly(IsJobReadOnly(data.jobs_by_pk));
}
}, [data, setJobReadOnly]);
@@ -41,7 +41,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader, setJobRe
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
}),
number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null
number: data ? data.jobs_by_pk?.ro_number : null
});
setBreadcrumbs([
@@ -52,7 +52,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader, setJobRe
{
link: `/manage/jobs/${jobId}/`,
label: t("titles.bc.jobs-detail", {
number: data ? data.jobs_by_pk && data.jobs_by_pk.ro_number : null
number: data ? data.jobs_by_pk?.ro_number : null
})
},
{
@@ -64,7 +64,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader, setJobRe
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!data.jobs_by_pk) return <NotFound />;
if (!data.jobs_by_pk) return <NotFound />;
if (!data.jobs_by_pk.job_totals)
return <Result title={t("jobs.errors.nofinancial")} extra={<JobCalculateTotals job={data.jobs_by_pk} />} />;

View File

@@ -1,7 +1,7 @@
import { PageHeader } from "@ant-design/pro-layout";
import { Button, Result, Space, Steps } from "antd";
import React, { useContext, useState } from "react";
import { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component";
@@ -63,7 +63,7 @@ export default function JobsCreateComponent({ form }) {
next();
form
.validateFields()
.then((r) => {
.then(() => {
if (steps[pageIndex].validation) {
setErrorMessage(null);
next();
@@ -83,7 +83,9 @@ export default function JobsCreateComponent({ form }) {
onClick={() => {
form
.validateFields()
.then((r) => {})
.then(() => {
// NO OP
})
.catch((error) => console.log("error", error));
form.submit();
}}
@@ -108,7 +110,7 @@ export default function JobsCreateComponent({ form }) {
setPageIndex(idx);
form
.validateFields()
.then((r) => {
.then(() => {
if (steps[pageIndex].validation) {
setErrorMessage(null);
setPageIndex(idx);

View File

@@ -1,7 +1,7 @@
import { useLazyQuery, useMutation } from "@apollo/client";
import { Form } from "antd";
import _ from "lodash";
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -48,7 +48,7 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const [loadOwner, RemoteOwnerData] = useLazyQuery(QUERY_OWNER_FOR_JOB_CREATION);
useEffect(() => {
if (!!state.owner.selectedid) {
if (state.owner.selectedid) {
loadOwner({
variables: { id: state.owner.selectedid }
});
@@ -111,7 +111,7 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
);
let ownerData;
if (!!!job.ownerid) {
if (!job.ownerid) {
ownerData = job.owner.data;
ownerData.shopid = bodyshop.id;
delete ownerData.preferred_contact;
@@ -122,7 +122,7 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
delete ownerData.__typename;
}
if (!state.vehicle.none) {
if (!!!job.vehicleid) {
if (!job.vehicleid) {
delete job.vehicleid;
job.vehicle.data.shopid = bodyshop.id;
job.plate_no = job.vehicle.data.plate_no;

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
@@ -59,7 +59,7 @@ export function JobsDeliverContainer({ bodyshop, setBreadcrumbs, setSelectedHead
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (data && !!!data.bodyshops_by_pk.deliverchecklist)
if (data && !data.bodyshops_by_pk.deliverchecklist)
return <AlertComponent message={t("deliver.errors.nochecklist")} type="error" />;
return (
<FeatureWrapperComponent

View File

@@ -260,7 +260,7 @@ export function JobsDetailPage({
form.setFieldsValue(transformJobToForm(job));
form.resetFields();
}
} catch (error) {
} catch {
notification["error"]({
message: t("jobs.errors.totalscalc")
});
@@ -487,7 +487,7 @@ export function JobsDetailPage({
export default connect(mapStateToProps, mapDispatchToProps)(JobsDetailPage);
const transformJobToForm = (job) => {
export const transformJobToForm = (job) => {
const transformedJob = { ...job };
transformedJob.parts_tax_rates = Object.keys(transformedJob.parts_tax_rates).reduce((acc, parttype) => {

View File

@@ -1,5 +1,5 @@
import { useMutation, useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
@@ -61,19 +61,19 @@ function JobsDetailPageContainer({ setBreadcrumbs, addRecentItem, setSelectedHea
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
}),
ro_number: (data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")
ro_number: 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")
number: (data?.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")
})
}
]);
if (data && data.jobs_by_pk) {
if (data?.jobs_by_pk) {
setJobReadOnly(IsJobReadOnly(data.jobs_by_pk));
addRecentItem(
@@ -90,7 +90,7 @@ function JobsDetailPageContainer({ setBreadcrumbs, addRecentItem, setSelectedHea
if (loading) return <SpinComponent />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!data.jobs_by_pk) return <NotFound />;
if (!data.jobs_by_pk) return <NotFound />;
return data.jobs_by_pk ? (
<RbacWrapper action="jobs:detail">

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
@@ -48,7 +48,7 @@ export function JobsIntakeContainer({ bodyshop, setBreadcrumbs, setSelectedHeade
{
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"))
number: data && (data.jobs_by_pk?.ro_number || t("general.labels.na"))
})
},
{
@@ -61,7 +61,7 @@ export function JobsIntakeContainer({ bodyshop, setBreadcrumbs, setSelectedHeade
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (data && !!!data.bodyshops_by_pk.intakechecklist)
if (data && !data.bodyshops_by_pk.intakechecklist)
return <AlertComponent message={t("intake.errors.nochecklist")} type="error" />;
return (
@@ -82,7 +82,7 @@ export function JobsIntakeContainer({ bodyshop, setBreadcrumbs, setSelectedHeade
<JobChecklist
type="intake"
checklistConfig={(data && data.bodyshops_by_pk.intakechecklist) || {}}
job={data && data.jobs_by_pk}
job={data?.jobs_by_pk}
/>
)}
</div>

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import JobDetailCards from "../../components/job-detail-cards/job-detail-cards.component";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { connect } from "react-redux";
import { useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
//import DashboardGridComponent from "../../components/dashboard-grid/dashboard-grid.component";
import { useNavigate } from "react-router-dom";

View File

@@ -1,21 +1,18 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ManageRootPageComponent from "./manage-root.page.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs))
});
export function ManageRootPageContainer({ setBreadcrumbs, bodyshop }) {
export function ManageRootPageContainer({ setBreadcrumbs }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.manageroot", {

View File

@@ -1,11 +1,10 @@
import { Button, FloatButton, Layout, Space, Spin } from "antd";
import { AlertOutlined, BulbOutlined } from "@ant-design/icons";
import { FloatButton, Layout, Spin } from "antd";
import { Route, Routes } from "react-router-dom";
// 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 { connect } from "react-redux";
import { Link, Route, Routes } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
import ChatAffixContainer from "../../components/chat-affix/chat-affix.container";
@@ -15,20 +14,19 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
//Component Imports
import * as Sentry from "@sentry/react";
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 LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
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 { selectBodyshop, selectInstanceConflict, selectPartsManagementOnly } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import "./manage.page.styles.scss";
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 { useSocket } from "../../contexts/SocketIO/useSocket.js";
import useAlertsNotifications from "../../hooks/useAlertsNotifications.jsx";
const PrintCenterModalContainer = lazy(
() => import("../../components/print-center-modal/print-center-modal.container")
);
const JobsPage = lazy(() => import("../jobs/jobs.page"));
@@ -104,83 +102,21 @@ const MyTasksPage = lazy(() => import("../tasks/myTasksPageContainer.jsx"));
const AllTasksPage = lazy(() => import("../tasks/allTasksPageContainer.jsx"));
const TaskUpsertModalContainer = lazy(() => import("../../components/task-upsert-modal/task-upsert-modal.container"));
const { Content, Footer } = Layout;
const { Content } = Layout;
const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict,
bodyshop: selectBodyshop,
alerts: selectAlerts
partsManagementOnly: selectPartsManagementOnly
});
const ALERT_FILE_URL = InstanceRenderManager({
imex: "https://images.imex.online/alerts/alerts-imex.json",
rome: "https://images.imex.online/alerts/alerts-rome.json"
});
const mapDispatchToProps = (dispatch) => ({
setAlerts: (alerts) => dispatch(addAlerts(alerts))
});
export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
export function Manage({ conflict, bodyshop, partsManagementOnly }) {
const { t } = useTranslation();
const [chatVisible] = useState(false);
const { socket, clientId } = useSocket();
const notification = useNotification();
// State to track displayed alerts
const [displayedAlertIds, setDisplayedAlertIds] = useState([]);
// Centralized alerts handling (fetch + dedupe + notifications)
useAlertsNotifications();
// Fetch displayed alerts from localStorage on mount
useEffect(() => {
const displayedAlerts = JSON.parse(localStorage.getItem("displayedAlerts") || "[]");
setDisplayedAlertIds(displayedAlerts);
}, []);
// Fetch alerts from the JSON file and dispatch to Redux store
useEffect(() => {
const fetchAlerts = async () => {
try {
const response = await fetch(ALERT_FILE_URL);
const fetchedAlerts = await response.json();
setAlerts(fetchedAlerts);
} catch (error) {
console.warn("Error fetching alerts:", error.message);
}
};
fetchAlerts().catch((err) => `Error fetching Bodyshop Alerts: ${err?.message || ""}`);
}, [setAlerts]);
// Use useEffect to watch for new alerts
useEffect(() => {
if (alerts && Object.keys(alerts).length > 0) {
// Convert the alerts object into an array
const alertArray = Object.values(alerts);
// Filter out alerts that have already been dismissed
const newAlerts = alertArray.filter((alert) => !displayedAlertIds.includes(alert.id));
newAlerts.forEach((alert) => {
// Display the notification
notification.open({
key: "notification-alerts-" + alert.id,
message: alert.message,
description: alert.description,
type: alert.type || "info",
duration: 0,
closable: true,
onClose: () => {
// When the notification is closed, update displayed alerts state and localStorage
setDisplayedAlertIds((prevIds) => {
const updatedIds = [...prevIds, alert.id];
localStorage.setItem("displayedAlerts", JSON.stringify(updatedIds));
return updatedIds;
});
}
});
});
}
}, [alerts, displayedAlertIds, notification]);
useEffect(() => {
window.Canny("initChangelog", {
appID: "680bd2c7ee501290377f6686",
@@ -197,6 +133,13 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
});
}, [t]);
// This is a required safety check to prevent parts management only users from accessing /manage based routes
if (partsManagementOnly && !import.meta.env.DEV) {
// This NEEDS to be done this way, DO NOT use the react router, it will cause a ton of side effects
window.location = "/parts";
return null; // Prevent further rendering
}
const AppRouteTable = (
<Suspense
fallback={
@@ -638,13 +581,6 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />;
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 (
<>
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
@@ -659,41 +595,10 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
<FloatButton.BackTop style={{ right: 100, bottom: 25 }} />
</Content>
<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 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>
<GlobalFooter />
</Layout>
</>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Manage);
export default connect(mapStateToProps)(Manage);

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import AlertComponent from "../../components/alert/alert.component";

View File

@@ -1,8 +0,0 @@
.content-container {
padding: 1rem;
}
.layout-container {
// height: 100vh;
// overflow-y: auto;
}

View File

@@ -1,5 +1,4 @@
import { Col, Divider, Row } from "antd";
import React from "react";
import OwnerDetailForm from "../../components/owner-detail-form/owner-detail-form.container";
import OwnerDetailJobsComponent from "../../components/owner-detail-jobs/owner-detail-jobs.component";

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useParams } from "react-router-dom";
@@ -57,7 +57,7 @@ export function OwnersDetailContainer({ setBreadcrumbs, addRecentItem, setSelect
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
if (!!!data.owners_by_pk) return <NotFound />;
if (!data.owners_by_pk) return <NotFound />;
return (
<RbacWrapper action="owners:detail">

View File

@@ -1,4 +1,3 @@
import React from "react";
import OwnersListContainer from "../../components/owners-list/owners-list.container";
export default function OwnersPageComponent() {

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import OwnersPageComponent from "./owners.page.component";
import { useTranslation } from "react-i18next";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import PartsQueueDetailCard from "../../components/parts-queue-card/parts-queue-card.component";

View File

@@ -0,0 +1,74 @@
import { Tabs } from "antd";
import queryString from "query-string";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import ShopVendorPageComponent from "../shop-vendor/shop-vendor.page.component";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import PartsShopInfoContainer from "../../components/parts-shop-info/parts-shop-info.container";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs))
});
export function PartsSettingsPage({ setSelectedHeader, setBreadcrumbs }) {
const { t } = useTranslation();
const history = useNavigate();
const search = queryString.parse(useLocation().search);
useEffect(() => {
document.title = t("titles.parts_settings", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
})
});
setSelectedHeader("settings");
setBreadcrumbs([
{
link: "/parts",
label: t("titles.bc.parts")
},
{
link: "/parts/settings",
label: t("titles.bc.parts_settings")
}
]);
}, [t, setSelectedHeader, setBreadcrumbs]);
useEffect(() => {
if (!search.tab) history({ search: "?tab=shop" });
}, [history, search]);
const items = [
{
key: "shop",
label: t("bodyshop.labels.parts_shop_management"),
children: (
<RbacWrapper action="shop:config">
<PartsShopInfoContainer />
</RbacWrapper>
)
},
{
key: "vendors",
label: t("bodyshop.labels.parts_vendor_management"),
children: (
<RbacWrapper action="shop:vendors">
<ShopVendorPageComponent />
</RbacWrapper>
)
}
];
return <Tabs activeKey={search.tab} onChange={(key) => history({ search: `?tab=${key}` })} items={items} />;
}
export default connect(mapStateToProps, mapDispatchToProps)(PartsSettingsPage);

View File

@@ -3,7 +3,6 @@ import { useQuery } from "@apollo/client";
import { Button, Card, Input, Space, Table, Typography } from "antd";
import _ from "lodash";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";
@@ -95,13 +94,13 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) {
title: t("phonebook.fields.phone1"),
dataIndex: "phone1",
key: "phone1",
render: (text, record) => <ChatOpenButton phone={text} />
render: (text) => <ChatOpenButton phone={text} />
},
{
title: t("phonebook.fields.phone2"),
dataIndex: "phone2",
key: "phone2",
render: (text, record) => <ChatOpenButton phone={text} />
render: (text) => <ChatOpenButton phone={text} />
},
{
title: t("phonebook.fields.address1"),
@@ -179,12 +178,12 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) {
pagination={{
position: "top",
pageSize: pageLimit,
current: parseInt(page || 1),
current: parseInt(page || 1, 10),
total: data && data.search_phonebook_aggregate.aggregate.count
}}
columns={columns}
rowKey="id"
dataSource={data && data.search_phonebook}
dataSource={data?.search_phonebook}
//scroll={{ x: true }}
onChange={handleTableChange}
rowSelection={{
@@ -192,9 +191,9 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) {
type: "radio",
selectedRowKeys: [phonebookentry]
}}
onRow={(record, rowIndex) => {
onRow={(record) => {
return {
onClick: (event) => {
onClick: () => {
handleOnRowClick(record);
}
};

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";

View File

@@ -1,14 +1,14 @@
import React from "react";
import ProductionBoardKanbanContainer from "../../components/production-board-kanban/production-board-kanban.container";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardComponent);
@@ -19,7 +19,7 @@ export function ProductionBoardComponent({ bodyshop }) {
} = useSplitTreatments({
attributes: {},
names: ["Production_Use_View"],
splitKey: bodyshop && bodyshop.imexshopid
splitKey: bodyshop?.imexshopid
});
return <ProductionBoardKanbanContainer subscriptionType={Production_Use_View.treatment} />;

View File

@@ -1,5 +1,5 @@
import { Card } from "antd";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -7,20 +7,17 @@ import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.com
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import UpsellComponent, { upsellEnum } from "../../components/upsell/upsell.component";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import ProductionBoardComponent from "./production-board.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
});
export function ProductionBoardContainer({ setBreadcrumbs, bodyshop, setSelectedHeader }) {
export function ProductionBoardContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
useEffect(() => {

View File

@@ -1,4 +1,3 @@
import React from "react";
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
import ProductionListTable from "../../components/production-list-table/production-list-table.container";
@@ -6,10 +5,11 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListComponent);
@@ -20,7 +20,7 @@ export function ProductionListComponent({ bodyshop }) {
} = useSplitTreatments({
attributes: {},
names: ["Production_Use_View"],
splitKey: bodyshop && bodyshop.imexshopid
splitKey: bodyshop?.imexshopid
});
return (

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
@@ -18,8 +18,7 @@ export function ProfileContainerPage({ setBreadcrumbs, setSelectedHeader }) {
document.title = t("titles.profile", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)",
rome: "$t(titles.romeonline)"
})
});
}, [t, setBreadcrumbs, setSelectedHeader]);

View File

@@ -1,5 +1,4 @@
import { Row } from "antd";
import React from "react";
import ProfileMyComponent from "../../components/profile-my/profile-my.component";
import ProfileShopsContainer from "../../components/profile-shops/profile-shops.container";

View File

@@ -1,4 +1,3 @@
import React from "react";
import { useLocation } from "react-router-dom";
import UserRequestResetPw from "../../components/user-request-pw-reset/user-request-reset-pw.component";
import UserValidatePwReset from "../../components/user-validate-pw-reset/user-validate-pw-reset.component";

View File

@@ -1,4 +1,3 @@
import React from "react";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import TimeTicketShift from "../../components/time-ticket-shift/time-ticket-shift.container";
import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component";

View File

@@ -1,6 +1,6 @@
import { useQuery } from "@apollo/client";
import { Card, Col, Row } from "antd";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";

View File

@@ -1,5 +1,4 @@
import { Drawer, Grid } from "antd";
import React from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import VendorsFormContainer from "../../components/vendors-form/vendors-form.container";
import VendorsListContainer from "../../components/vendors-list/vendors-list.container";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import ShopVendorPageComponent from "./shop-vendor.page.component";

View File

@@ -1,6 +1,6 @@
import { Tabs } from "antd";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";
@@ -100,7 +100,7 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
children: <ShopInfoConsentComponent bodyshop={bodyshop} />
});
}
return (
<RbacWrapper action="shop:config">
<Tabs activeKey={search.tab} onChange={(key) => history({ search: `?tab=${key}` })} items={items} />

View File

@@ -1,4 +1,3 @@
import React from "react";
import SignIn from "../../components/sign-in-form/sign-in-form.component";
export default function SignInPage() {

View File

@@ -0,0 +1,186 @@
import { BarsOutlined, PrinterFilled, SyncOutlined, ToolFilled } from "@ant-design/icons";
import { PageHeader } from "@ant-design/pro-layout";
import { useQuery } from "@apollo/client";
import { Button, Divider, Form, Space, Tabs } from "antd";
import queryString from "query-string";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
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.jsx";
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container.jsx";
import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx";
import JobProfileDataWarning from "../../components/job-profile-data-warning/job-profile-data-warning.component.jsx";
import JobsChangeStatus from "../../components/jobs-change-status/jobs-change-status.component.jsx";
import JobsDetailHeaderActions from "../../components/jobs-detail-header-actions/jobs-detail-header-actions.component.jsx";
import JobsDetailHeader from "../../components/jobs-detail-header/jobs-detail-header.component.jsx";
import JobsDetailPliContainer from "../../components/jobs-detail-pli/jobs-detail-pli.container.jsx";
import { QUERY_PARTS_BILLS_BY_JOBID } from "../../graphql/bills.queries.js";
import { selectIsPartsEntry, selectJobReadOnly } from "../../redux/application/application.selectors.js";
import { setModalContext } from "../../redux/modals/modals.actions.js";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
isPartsEntry: selectIsPartsEntry
});
const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "printCenter"
})
)
});
export function SimplifiedPartsJobDetailComponent({ setPrintCenterContext, jobRO, job, refetch, isPartsEntry }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const history = useNavigate();
const [loading] = useState(false);
const search = queryString.parse(useLocation().search);
const billsQuery = useQuery(QUERY_PARTS_BILLS_BY_JOBID, {
variables: { jobid: job.id },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
useEffect(() => {
//form.setFieldsValue(transormJobToForm(job));
form.resetFields();
}, [form, job]);
const handleBillOnRowClick = (record) => {
if (record) {
if (record.id) {
search.billid = record.id;
history({ search: queryString.stringify(search) });
}
} else {
delete search.billid;
history({ search: queryString.stringify(search) });
}
};
const handlePartsOrderOnRowClick = (record) => {
if (record) {
if (record.id) {
search.partsorderid = record.id;
history({ search: queryString.stringify(search) });
}
} else {
delete search.partsorderid;
history({ search: queryString.stringify(search) });
}
};
const handlePartsDispatchOnRowClick = (record) => {
if (record) {
if (record.id) {
search.partsdispatchid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.partsdispatchid;
history.push({ search: queryString.stringify(search) });
}
};
const menuExtra = (
<Space wrap>
<Button
onClick={() => {
refetch();
}}
key="refresh"
>
<SyncOutlined />
{t("general.labels.refresh")}
</Button>
<JobsChangeStatus job={job} />
<Button
onClick={() => {
setPrintCenterContext({
actions: { refetch: refetch },
context: {
id: job.id,
job: job,
type: "job"
}
});
}}
key="printing"
>
<PrinterFilled />
{t("jobs.actions.printCenter")}
</Button>
{!isPartsEntry && (
<>
<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>
<JobLineUpsertModalContainer />
<PageHeader title={<Space>{job.ro_number || t("general.labels.na")}</Space>} extra={menuExtra} />
<JobsDetailHeader job={job} disabled={true} />
<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: "repairdata",
icon: <BarsOutlined />,
id: "job-details-repairdata",
label: t("menus.jobsdetail.repairdata"),
forceRender: true,
children: (
<JobsLinesContainer
job={job}
joblines={job.joblines}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
handlePartsDispatchOnRowClick={handlePartsDispatchOnRowClick}
refetch={refetch}
form={form}
/>
)
},
{
key: "partssublet",
id: "job-details-partssublet",
icon: <ToolFilled />,
label: t("menus.jobsdetail.partssublet"),
children: (
<JobsDetailPliContainer
job={job}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
handlePartsDispatchOnRowClick={handlePartsDispatchOnRowClick}
/>
)
}
]}
/>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SimplifiedPartsJobDetailComponent);

View File

@@ -0,0 +1,103 @@
import { useQuery } from "@apollo/client";
import { 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 RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { GET_JOB_BY_PK } 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 InstanceRenderManager from "../../utils/instanceRenderMgr";
import IsJobReadOnly from "../../utils/jobReadOnly";
import SimplifiedPartsJobsDetailComponent from "./simplified-parts-jobs-detail.component";
const mapStateToProps = createStructuredSelector({
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))
});
function SimplifiedPartsJobsDetailContainer({ 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"
});
useEffect(() => {
setSelectedHeader("activejobs");
document.title = loading
? InstanceRenderManager({
imex: t("titles.imexonline"),
rome: t("titles.romeonline")
})
: error
? InstanceRenderManager({
imex: t("titles.imexonline"),
rome: t("titles.romeonline")
})
: t("titles.jobsdetail", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
}),
ro_number: (data.jobs_by_pk && data.jobs_by_pk.ro_number) || t("general.labels.na")
});
setBreadcrumbs([
{ link: "/parts/", label: t("titles.bc.jobs") },
{
link: `/parts/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)}`,
`/parts/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">
<SimplifiedPartsJobsDetailComponent job={data.jobs_by_pk} refetch={refetch} />
</RbacWrapper>
) : (
<AlertComponent message={t("jobs.errors.noaccess")} type="error" />
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SimplifiedPartsJobsDetailContainer);

View File

@@ -0,0 +1,36 @@
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import SimplifiedPartsJobsListContainer from "../../components/simplified-parts-jobs-list/simplified-parts-jobs-list.container";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
});
export function SimplifiedPartsJobsPage({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.simplified-parts-jobs", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
})
});
setSelectedHeader("parts-queue");
setBreadcrumbs([{ link: "/parts", label: t("titles.bc.simplified-parts-jobs") }]);
}, [setBreadcrumbs, t, setSelectedHeader]);
return (
<RbacWrapper action="jobs:partsqueue">
<SimplifiedPartsJobsListContainer />
{/* <PartsQueueDetailCard /> */}
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(SimplifiedPartsJobsPage);

View File

@@ -0,0 +1,142 @@
import * as Sentry from "@sentry/react";
import { FloatButton, Layout, Spin } from "antd";
import { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Route, Routes } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component.jsx";
import ConflictComponent from "../../components/conflict/conflict.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 ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component.jsx";
import UpdateAlert from "../../components/update-alert/update-alert.component.jsx";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors.js";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import useAlertsNotifications from "../../hooks/useAlertsNotifications.jsx";
const SimplifiedPartsJobsPage = lazy(() => import("../simplified-parts-jobs/simplified-parts-jobs.page.jsx"));
const SimplifiedPartsJobsDetailPage = lazy(
() => import("../simplified-parts-jobs-detail/simplified-parts-jobs-detail.container.jsx")
);
const PartsSettingsPage = lazy(() => import("../parts-settings/parts-settings.page.component.jsx"));
const ShopVendorPageContainer = lazy(() => import("../shop-vendor/shop-vendor.page.container.jsx"));
const EmailOverlayContainer = lazy(() => import("../../components/email-overlay/email-overlay.container.jsx"));
const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container.jsx"));
const PrintCenterModalContainer = lazy(
() => import("../../components/print-center-modal/print-center-modal.container")
);
const VehiclesContainer = lazy(() => import("../vehicles/vehicles.page.container.jsx"));
const VehiclesDetailContainer = lazy(() => import("../vehicles-detail/vehicles-detail.page.container.jsx"));
const { Content } = Layout;
const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict,
bodyshop: selectBodyshop
});
export function SimplifiedPartsPage({ conflict, bodyshop }) {
const { t } = useTranslation();
// Centralized alerts handling (fetch + dedupe + notifications)
useAlertsNotifications();
useEffect(() => {
document.title = InstanceRenderManager({
imex: t("titles.imexonline"),
rome: t("titles.romeonline")
});
}, [t]);
const AppRouteTable = (
<Suspense
fallback={
<LoadingSpinner
message={t("general.labels.loadingapp", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)"
})
})}
/>
}
>
<BreadCrumbs />
<ReportCenterModal />
<EmailOverlayContainer />
<PrintCenterModalContainer />
<Routes>
<Route
path="/"
element={
<Suspense fallback={<Spin />}>
<SimplifiedPartsJobsPage />
</Suspense>
}
/>
<Route
path="/jobs/:jobId"
element={
<Suspense fallback={<Spin />}>
<SimplifiedPartsJobsDetailPage />
</Suspense>
}
/>
<Route
path="/vehicles"
element={
<Suspense fallback={<Spin />}>
<VehiclesContainer />
</Suspense>
}
/>
<Route
path="/vehicles/:vehId"
element={
<Suspense fallback={<Spin />}>
<VehiclesDetailContainer />
</Suspense>
}
/>
<Route
path="/shop/vendors"
element={
<Suspense fallback={<Spin />}>
<ShopVendorPageContainer />
</Suspense>
}
/>
<Route
path="/settings"
element={
<Suspense fallback={<Spin />}>
<PartsSettingsPage />
</Suspense>
}
/>
</Routes>
</Suspense>
);
let PageContent;
if (conflict) PageContent = <ConflictComponent />;
else if (bodyshop && bodyshop.sub_status !== "active") PageContent = <ShopSubStatusComponent />;
else PageContent = AppRouteTable;
return (
<Layout style={{ minHeight: "100vh" }} className="layout-container">
<UpdateAlert />
<Content className="content-container">
<Sentry.ErrorBoundary fallback={<ErrorBoundary />} showDialog>
{PageContent}
</Sentry.ErrorBoundary>
<FloatButton.BackTop style={{ right: 100, bottom: 25 }} />
</Content>
<GlobalFooter />
</Layout>
);
}
export default connect(mapStateToProps)(SimplifiedPartsPage);

View File

@@ -0,0 +1,35 @@
import { useQuery } from "@apollo/client";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import AlertComponent from "../../components/alert/alert.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
import { setBodyshop } from "../../redux/user/user.actions";
import SimplifiedPartsPage from "./simplified-parts.page.component";
const mapDispatchToProps = (dispatch) => ({
setBodyshop: (bs) => dispatch(setBodyshop(bs))
});
function SimplifiedPartsPageContainer({ setBodyshop }) {
const { loading, error, data } = useQuery(QUERY_BODYSHOP, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
const { t } = useTranslation();
useEffect(() => {
if (data) {
setBodyshop(data.bodyshops[0] || { notfound: true });
}
}, [data, setBodyshop]);
if (loading) return <LoadingSpinner message={t("general.labels.loadingshop")} />;
if (error) return <AlertComponent message={error.message} type="error" />;
return <SimplifiedPartsPage />;
}
export default connect(null, mapDispatchToProps)(SimplifiedPartsPageContainer);

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import React, { useState } from "react";
import { useState } from "react";
import { Button, Card, Input, Space, Table } from "antd";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";

View File

@@ -2,7 +2,6 @@ import { MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined } from "@ant-design
import { useQuery } from "@apollo/client";
import { Button, Card, Space, Table } from "antd";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";
@@ -20,7 +19,7 @@ const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({});
const mapDispatchToProps = () => ({});
export function TechDispatchedParts({ technician, bodyshop }) {
const searchParams = queryString.parse(useLocation().search);
@@ -83,12 +82,20 @@ export function TechDispatchedParts({ technician, bodyshop }) {
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => <Button onClick={() => {}}>{t("timetickets.actions.claimtasks")}</Button>
render: () => (
<Button
onClick={() => {
// NO OP
}}
>
{t("timetickets.actions.claimtasks")}
</Button>
)
}
]
: [])
];
const handleTableChange = (pagination, filters, sorter) => {
const handleTableChange = (pagination) => {
searchParams.page = pagination.current;
history({ search: queryString.stringify(searchParams) });
};
@@ -107,7 +114,7 @@ export function TechDispatchedParts({ technician, bodyshop }) {
loading={loading}
pagination={{
pageSize: 25,
current: parseInt(page || 1),
current: parseInt(page || 1, 10),
total: data ? data.parts_dispatch_aggregate.aggregate.count : 0,
showSizeChanger: false
}}
@@ -118,7 +125,7 @@ export function TechDispatchedParts({ technician, bodyshop }) {
onChange={handleTableChange}
expandable={{
expandedRowRender: (record) => <PartsDispatchExpander dispatch={record} />,
rowExpandable: (record) => true,
rowExpandable: () => true,
//expandRowByClick: true,
expandIcon: ({ expanded, onExpand, record }) =>
expanded ? (

View File

@@ -1,5 +1,5 @@
import { Divider } from "antd";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import TechClockInFormContainer from "../../components/tech-job-clock-in-form/tech-job-clock-in-form.container";
import TechClockedInList from "../../components/tech-job-clocked-in-list/tech-job-clocked-in-list.component";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import TimeTicketShift from "../../components/time-ticket-shift/time-ticket-shift.container";
import InstanceRenderManager from "../../utils/instanceRenderMgr";

View File

@@ -1,5 +1,5 @@
import { Card, FloatButton, Layout } from "antd";
import React, { lazy, Suspense, useEffect } from "react";
import { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Route, Routes, useNavigate } from "react-router-dom";
@@ -40,7 +40,7 @@ const { Content } = Layout;
const mapStateToProps = createStructuredSelector({
technician: selectTechnician
});
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { connect } from "react-redux";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";

View File

@@ -1,5 +1,4 @@
import { useQuery } from "@apollo/client";
import React from "react";
import AlertComponent from "../../components/alert/alert.component";
import JobsDocumentsContainer from "../../components/jobs-documents-gallery/jobs-documents-gallery.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
@@ -14,7 +13,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = () => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(TemporaryDocsComponent);
@@ -25,7 +24,7 @@ export function TemporaryDocsComponent({ bodyshop }) {
} = useSplitTreatments({
attributes: {},
names: ["Imgproxy"],
splitKey: bodyshop && bodyshop.imexshopid
splitKey: bodyshop?.imexshopid
});
const { loading, error, data, refetch } = useQuery(QUERY_TEMPORARY_DOCS, {
@@ -41,14 +40,12 @@ export function TemporaryDocsComponent({ bodyshop }) {
return <JobsDocumentsLocalGallery job={{ id: "temporary" }} />;
}
return (
<>
<JobsDocumentsContainer
documentsList={data ? data.documents : []}
jobId={null}
billId={null}
refetchOverride={refetch}
ignoreSizeLimit
/>
</>
<JobsDocumentsContainer
documentsList={data ? data.documents : []}
jobId={null}
billId={null}
refetchOverride={refetch}
ignoreSizeLimit
/>
);
}

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";

View File

@@ -1,7 +1,7 @@
import { useQuery } from "@apollo/client";
import { Card, Col, Row, Space } from "antd";
import dayjs from "../../utils/day";
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useSearchParams } from "react-router-dom";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";

View File

@@ -1,5 +1,4 @@
import { Col, Divider, Row } from "antd";
import React from "react";
import VehicleDetailFormContainer from "../../components/vehicle-detail-form/vehicle-detail-form.container";
import VehicleDetailJobsComponent from "../../components/vehicle-detail-jobs/vehicle-detail-jobs.component";

View File

@@ -1,4 +1,4 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import VehicleDetailComponent from "./vehicles-detail.page.component";
import { useQuery } from "@apollo/client";
import { useParams } from "react-router-dom";
@@ -11,6 +11,13 @@ import { connect } from "react-redux";
import { CreateRecentItem } from "../../utils/create-recent-item";
import NotFound from "../../components/not-found/not-found.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { selectIsPartsEntry } from "../../redux/application/application.selectors";
import { createStructuredSelector } from "reselect";
import getPartsBasePath from "../../utils/getPartsBasePath.js";
const mapStateToProps = createStructuredSelector({
isPartsEntry: selectIsPartsEntry
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -18,9 +25,10 @@ const mapDispatchToProps = (dispatch) => ({
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
});
export function VehicleDetailContainer({ setBreadcrumbs, addRecentItem, setSelectedHeader }) {
export function VehicleDetailContainer({ setBreadcrumbs, addRecentItem, setSelectedHeader, isPartsEntry }) {
const { vehId } = useParams();
const { t } = useTranslation();
const basePath = getPartsBasePath(isPartsEntry);
const { loading, data, error, refetch } = useQuery(QUERY_VEHICLE_BY_ID, {
variables: { id: vehId },
@@ -43,20 +51,22 @@ export function VehicleDetailContainer({ setBreadcrumbs, addRecentItem, setSelec
});
setSelectedHeader("vehicles");
setBreadcrumbs([
{ link: "/manage/vehicles", label: t("titles.bc.vehicles") },
{
link: `/manage/vehicles/${vehId}`,
label: t("titles.bc.vehicle-details", {
vehicle:
data && data.vehicles_by_pk
? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
} ${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""}`
: ""
})
}
]);
const crumbs = [];
if (isPartsEntry) crumbs.push({ link: "/parts/", label: t("titles.bc.jobs") });
crumbs.push({ link: `${basePath}/vehicles`, label: t("titles.bc.vehicles") });
crumbs.push({
link: `${basePath}/vehicles/${vehId}`,
label: t("titles.bc.vehicle-details", {
vehicle:
data && data.vehicles_by_pk
? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
} ${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""}`
: ""
})
});
setBreadcrumbs(crumbs);
if (data && data.vehicles_by_pk)
addRecentItem(
@@ -66,10 +76,10 @@ export function VehicleDetailContainer({ setBreadcrumbs, addRecentItem, setSelec
`${data.vehicles_by_pk.v_vin || "N/A"} | ${
data.vehicles_by_pk.v_model_yr || ""
} ${data.vehicles_by_pk.v_make_desc || ""} ${data.vehicles_by_pk.v_model_desc || ""}`.trim(),
`/manage/vehicles/${vehId}`
`${basePath}/vehicles/${vehId}`
)
);
}, [t, data, setBreadcrumbs, vehId, addRecentItem, setSelectedHeader]);
}, [t, data, setBreadcrumbs, vehId, addRecentItem, setSelectedHeader, basePath, isPartsEntry]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
@@ -77,4 +87,4 @@ export function VehicleDetailContainer({ setBreadcrumbs, addRecentItem, setSelec
return <VehicleDetailComponent vehicle={data.vehicles_by_pk} refetch={refetch} />;
}
export default connect(null, mapDispatchToProps)(VehicleDetailContainer);
export default connect(mapStateToProps, mapDispatchToProps)(VehicleDetailContainer);

View File

@@ -1,4 +1,3 @@
import React from "react";
import VehiclesListContainer from "../../components/vehicles-list/vehicles-list.container";
export default function VehiclesPageComponent() {

View File

@@ -1,18 +1,26 @@
import React, { useEffect } from "react";
import { useEffect } from "react";
import VehiclesPageComponent from "./vehicles.page.component";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { createStructuredSelector } from "reselect";
import { selectIsPartsEntry } from "../../redux/application/application.selectors.js";
import getPartsBasePath from "../../utils/getPartsBasePath.js";
const mapStateToProps = createStructuredSelector({
isPartsEntry: selectIsPartsEntry
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
});
export function VehiclesPageContainer({ setBreadcrumbs, setSelectedHeader }) {
export function VehiclesPageContainer({ setBreadcrumbs, setSelectedHeader, isPartsEntry }) {
const { t } = useTranslation();
const basePath = getPartsBasePath(isPartsEntry);
useEffect(() => {
document.title = t("titles.vehicles", {
@@ -22,10 +30,18 @@ export function VehiclesPageContainer({ setBreadcrumbs, setSelectedHeader }) {
})
});
setSelectedHeader("vehicles");
setBreadcrumbs([{ link: "/manage/vehicles", label: t("titles.bc.vehicles") }]);
}, [t, setBreadcrumbs, setSelectedHeader]);
if (isPartsEntry) {
setBreadcrumbs([
{ link: "/parts/", label: t("titles.bc.jobs") },
{ link: `${basePath}/vehicles`, label: t("titles.bc.vehicles") }
]);
} else {
setBreadcrumbs([{ link: `${basePath}/vehicles`, label: t("titles.bc.vehicles") }]);
}
}, [t, setBreadcrumbs, setSelectedHeader, basePath, isPartsEntry]);
return <VehiclesPageComponent />;
}
export default connect(null, mapDispatchToProps)(VehiclesPageContainer);
export default connect(mapStateToProps, mapDispatchToProps)(VehiclesPageContainer);