diff --git a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx index 7397af54d..58fba506a 100644 --- a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx +++ b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx @@ -9,7 +9,7 @@ import { createStructuredSelector } from "reselect"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { insertAuditTrail } from "../../redux/application/application.actions.js"; -import { selectJobReadOnly } from "../../redux/application/application.selectors"; +import { selectIsPartsEntry, selectJobReadOnly } from "../../redux/application/application.selectors"; import { setModalContext } from "../../redux/modals/modals.actions"; import { selectBodyshop, selectPartsManagementOnly } from "../../redux/user/user.selectors"; import AuditTrailMapping from "../../utils/AuditTrailMappings.js"; @@ -32,7 +32,8 @@ import "./jobs-detail-header.styles.scss"; const mapStateToProps = createStructuredSelector({ jobRO: selectJobReadOnly, bodyshop: selectBodyshop, - partsManagementOnly: selectPartsManagementOnly + partsManagementOnly: selectPartsManagementOnly, + isPartsEntry: selectIsPartsEntry }); const mapDispatchToProps = (dispatch) => ({ @@ -53,11 +54,12 @@ const mapDispatchToProps = (dispatch) => ({ ) }); -export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, partsManagementOnly }) { +export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, partsManagementOnly, isPartsEntry }) { const { t } = useTranslation(); const { notification } = useNotification(); const [notesClamped, setNotesClamped] = useState(true); const [updateJob] = useMutation(UPDATE_JOB); + const basePath = isPartsEntry ? "/parts" : "/manage"; const colSpan = { xs: { span: 24 }, @@ -107,127 +109,127 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, pa return ( <> - - -
- - - {job.status} - {job.inproduction && {t("jobs.labels.inproduction")}} - {job.suspended && } - {job.iouparent && ( - - - - - - )} - {job.production_vars && job.production_vars.alert ? ( - - ) : null} - {job.status === bodyshop.md_ro_statuses.default_scheduled && job.scheduled_in ? ( - - - {job.scheduled_in} - - - ) : null} - - - - - - {job.ins_co_nm} - {job.clm_no} - - {job.po_number} - - - {job.clm_total} - / - {job.owner_owing} - - - {!partsManagementOnly && ( - <> - - {job.alt_transport} - - - {job?.cccontracts?.length > 0 && ( - - {job.cccontracts.map((c, index) => ( - - - {`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`} - {index !== job.cccontracts.length - 1 ? "," : null} - - - ))} - - )} - - - - - - handleCheckboxChange("estimate_sent_approval", e.target.checked)} - disabled={disabled} - > - {job.estimate_sent_approval && ( - - {job.estimate_sent_approval} - - )} - - - - - - handleCheckboxChange("estimate_approved", e.target.checked)} - disabled={disabled} - > - {job.estimate_approved && ( - - {job.estimate_approved} - - )} - - - + + +
+ - {job.special_coverage_policy && ( - - - - {t("jobs.labels.specialcoveragepolicy")} - - + {job.status} + {job.inproduction && {t("jobs.labels.inproduction")}} + {job.suspended && } + {job.iouparent && ( + + + + + )} - {job.ca_gst_registrant && ( - - - - {t("jobs.fields.ca_gst_registrant")} - + {job.production_vars && job.production_vars.alert ? ( + + ) : null} + {job.status === bodyshop.md_ro_statuses.default_scheduled && job.scheduled_in ? ( + + + {job.scheduled_in} + - )} - {job.hit_and_run && ( - - - - {t("jobs.fields.hit_and_run")} - - - )} + ) : null} - - )} -
+ + + + + {job.ins_co_nm} + {job.clm_no} + + {job.po_number} + + + {job.clm_total} + / + {job.owner_owing} + + + {!partsManagementOnly && ( + <> + + {job.alt_transport} + + + {job?.cccontracts?.length > 0 && ( + + {job.cccontracts.map((c, index) => ( + + + {`${c.agreementnumber} - ${c.courtesycar.fleetnumber} ${c.courtesycar.year} ${c.courtesycar.make} ${c.courtesycar.model}`} + {index !== job.cccontracts.length - 1 ? "," : null} + + + ))} + + )} + + + + + + handleCheckboxChange("estimate_sent_approval", e.target.checked)} + disabled={disabled} + > + {job.estimate_sent_approval && ( + + {job.estimate_sent_approval} + + )} + + + + + + handleCheckboxChange("estimate_approved", e.target.checked)} + disabled={disabled} + > + {job.estimate_approved && ( + + {job.estimate_approved} + + )} + + + + + {job.special_coverage_policy && ( + + + + {t("jobs.labels.specialcoveragepolicy")} + + + )} + {job.ca_gst_registrant && ( + + + + {t("jobs.fields.ca_gst_registrant")} + + + )} + {job.hit_and_run && ( + + + + {t("jobs.fields.hit_and_run")} + + + )} + + + )} +
@@ -289,7 +291,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, pa disabled ? ( <>{vehicleTitle.length > 0 ? vehicleTitle : t("vehicles.labels.novehinfo")} ) : ( - + {vehicleTitle.length > 0 ? vehicleTitle : t("vehicles.labels.novehinfo")} ) @@ -344,22 +346,22 @@ export function JobsDetailHeader({ job, bodyshop, disabled, insertAuditTrail, pa {!partsManagementOnly && ( - - {t("jobs.labels.employeeassignments")}} - id={"job-employee-assignments"} - > -
- - - - {bodyHrs.toFixed(1)} / {refinishHrs.toFixed(1)} / {(bodyHrs + refinishHrs).toFixed(1)} - -
-
- - )} + + {t("jobs.labels.employeeassignments")}} + id={"job-employee-assignments"} + > +
+ + + + {bodyHrs.toFixed(1)} / {refinishHrs.toFixed(1)} / {(bodyHrs + refinishHrs).toFixed(1)} + +
+
+ + )}
diff --git a/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx b/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx index e224f151d..73db0c5e4 100644 --- a/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx +++ b/client/src/components/simplified-parts-jobs-list/simplified-parts-jobs-list.component.jsx @@ -1,4 +1,4 @@ -import { SettingOutlined, SyncOutlined } from "@ant-design/icons"; +import { CarOutlined, SettingOutlined, SyncOutlined } from "@ant-design/icons"; import { Button, Card, Input, Space, Table, Typography } from "antd"; import axios from "axios"; import _ from "lodash"; @@ -16,22 +16,23 @@ import useLocalStorage from "../../utils/useLocalStorage"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; +import { selectIsPartsEntry } from "../../redux/application/application.selectors"; +import * as Sentry from "@sentry/react"; const mapStateToProps = createStructuredSelector({ //currentUser: selectCurrentUser - bodyshop: selectBodyshop -}); -const mapDispatchToProps = (dispatch) => ({ - //setUserLanguage: language => dispatch(setUserLanguage(language)) + bodyshop: selectBodyshop, + isPartsEntry: selectIsPartsEntry }); -export function SimplifiedPartsJobsListComponent({ bodyshop, refetch, loading, jobs, total }) { +export function SimplifiedPartsJobsListComponent({ bodyshop, refetch, loading, jobs, total, isPartsEntry }) { const search = queryString.parse(useLocation().search); const [openSearchResults, setOpenSearchResults] = useState([]); const [searchLoading, setSearchLoading] = useState(false); const [filter, setFilter] = useLocalStorage("filter_jobs_all", null); const { page, sortcolumn, sortorder } = search; const history = useNavigate(); + const basePath = isPartsEntry ? "/parts" : "/manage"; const { t } = useTranslation(); const columns = [ @@ -92,7 +93,7 @@ export function SimplifiedPartsJobsListComponent({ bodyshop, refetch, loading, j ellipsis: true, render: (text, record) => { return record.vehicleid ? ( - + {`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${record.v_model_desc || ""}`} ) : ( @@ -171,7 +172,6 @@ export function SimplifiedPartsJobsListComponent({ bodyshop, refetch, loading, j if (search.search && search.search.trim() !== "") { searchJobs(); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); async function searchJobs(value) { @@ -183,7 +183,7 @@ export function SimplifiedPartsJobsListComponent({ bodyshop, refetch, loading, j }); setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source)); } catch (error) { - console.log("Error while fetching search results", error); + Sentry.captureMessage(`Error while fetching search results: ${error.message}`, "warning"); } finally { setSearchLoading(false); } @@ -193,6 +193,9 @@ export function SimplifiedPartsJobsListComponent({ bodyshop, refetch, loading, j + @@ -60,7 +67,7 @@ export default function VehicleTagPopoverComponent({ job }) { {job.vehicleid ? ( - + {`${job.v_model_yr || ""} ${job.v_color || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""} | @@ -78,3 +85,5 @@ export default function VehicleTagPopoverComponent({ job }) { ); } + +export default connect(mapStateToProps)(VehicleTagPopoverComponent); diff --git a/client/src/components/vehicles-list/vehicles-list.component.jsx b/client/src/components/vehicles-list/vehicles-list.component.jsx index f2e4a90dd..99e01b215 100644 --- a/client/src/components/vehicles-list/vehicles-list.component.jsx +++ b/client/src/components/vehicles-list/vehicles-list.component.jsx @@ -1,14 +1,14 @@ import { SyncOutlined } from "@ant-design/icons"; import { Button, Card, Input, Space, Table, Typography } from "antd"; import queryString from "query-string"; -import React, { useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Link, useLocation, useNavigate } from "react-router-dom"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import { pageLimit } from "../../utils/config"; import { alphaSort } from "../../utils/sorters"; -export default function VehiclesListComponent({ loading, vehicles, total, refetch }) { +export default function VehiclesListComponent({ loading, vehicles, total, refetch, basePath = "/manage" }) { const search = queryString.parse(useLocation().search); const { page @@ -31,7 +31,7 @@ export default function VehiclesListComponent({ loading, vehicles, total, refetc sorter: (a, b) => alphaSort(a.v_vin, b.v_vin), sortOrder: state.sortedInfo.columnKey === "v_vin" && state.sortedInfo.order, render: (text, record) => ( - + {record.v_vin || "N/A"} ) diff --git a/client/src/components/vehicles-list/vehicles-list.container.jsx b/client/src/components/vehicles-list/vehicles-list.container.jsx index 504270153..512a5cb0b 100644 --- a/client/src/components/vehicles-list/vehicles-list.container.jsx +++ b/client/src/components/vehicles-list/vehicles-list.container.jsx @@ -1,4 +1,3 @@ -import React from "react"; import VehiclesListComponent from "./vehicles-list.component"; import { useQuery } from "@apollo/client"; import AlertComponent from "../alert/alert.component"; @@ -6,10 +5,18 @@ import { QUERY_ALL_VEHICLES_PAGINATED } from "../../graphql/vehicles.queries"; import queryString from "query-string"; import { useLocation } from "react-router-dom"; import { pageLimit } from "../../utils/config"; +import { connect } from "react-redux"; +import { selectIsPartsEntry } from "../../redux/application/application.selectors"; +import { createStructuredSelector } from "reselect"; -export default function VehiclesListContainer() { +const mapStateToProps = createStructuredSelector({ + isPartsEntry: selectIsPartsEntry +}); + +export function VehiclesListContainer({ isPartsEntry }) { const searchParams = queryString.parse(useLocation().search); const { page, sortcolumn, sortorder, search } = searchParams; + const basePath = isPartsEntry ? "/parts" : "/manage"; const { loading, error, data, refetch } = useQuery(QUERY_ALL_VEHICLES_PAGINATED, { variables: { @@ -33,6 +40,9 @@ export default function VehiclesListContainer() { vehicles={data ? data.search_vehicles : null} total={data ? data.search_vehicles_aggregate.aggregate.count : 0} refetch={refetch} + basePath={basePath} /> ); } + +export default connect(mapStateToProps)(VehiclesListContainer); diff --git a/client/src/pages/simplified-parts/simplified-parts.page.component.jsx b/client/src/pages/simplified-parts/simplified-parts.page.component.jsx index 80ca2ed51..1f31814ad 100644 --- a/client/src/pages/simplified-parts/simplified-parts.page.component.jsx +++ b/client/src/pages/simplified-parts/simplified-parts.page.component.jsx @@ -29,6 +29,8 @@ const ReportCenterModal = lazy(() => import("../../components/report-center-moda 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({ @@ -146,6 +148,22 @@ export function SimplifiedPartsPage({ conflict, bodyshop, alerts, setAlerts }) { } /> + }> + + + } + /> + }> + + + } + /> ({ setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), @@ -18,9 +24,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 = isPartsEntry ? "/parts" : "/manage"; const { loading, data, error, refetch } = useQuery(QUERY_VEHICLE_BY_ID, { variables: { id: vehId }, @@ -43,20 +50,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 +75,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 ; if (error) return ; @@ -77,4 +86,4 @@ export function VehicleDetailContainer({ setBreadcrumbs, addRecentItem, setSelec return ; } -export default connect(null, mapDispatchToProps)(VehicleDetailContainer); +export default connect(mapStateToProps, mapDispatchToProps)(VehicleDetailContainer); diff --git a/client/src/pages/vehicles/vehicles.page.container.jsx b/client/src/pages/vehicles/vehicles.page.container.jsx index 539c09dbb..090c83fb0 100644 --- a/client/src/pages/vehicles/vehicles.page.container.jsx +++ b/client/src/pages/vehicles/vehicles.page.container.jsx @@ -1,18 +1,25 @@ -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"; + +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 = isPartsEntry ? "/parts" : "/manage"; useEffect(() => { document.title = t("titles.vehicles", { @@ -22,10 +29,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 ; } -export default connect(null, mapDispatchToProps)(VehiclesPageContainer); +export default connect(mapStateToProps, mapDispatchToProps)(VehiclesPageContainer);