@@ -1,20 +1,20 @@
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { SplitFactoryProvider, useSplitClient } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect, useSelector } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
import { setDarkMode } from "../redux/application/application.actions";
|
||||
import { selectDarkMode } from "../redux/application/application.selectors";
|
||||
import { selectCurrentUser } from "../redux/user/user.selectors.js";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import getTheme from "./themeProvider";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectCurrentUser } from "../redux/user/user.selectors.js";
|
||||
import { selectDarkMode } from "../redux/application/application.selectors";
|
||||
import { setDarkMode } from "../redux/application/application.actions";
|
||||
|
||||
// Base Split configuration
|
||||
const config = {
|
||||
@@ -85,7 +85,7 @@ function AppContainer({ currentUser, setDarkMode }) {
|
||||
theme={theme}
|
||||
form={{
|
||||
validateMessages: {
|
||||
required: t("general.validation.required", { label: "{{label}}" })
|
||||
required: t("general.validation.required", { label: "${label}" })
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -22,9 +22,9 @@ export function BreadCrumbs({ breadcrumbs, bodyshop, isPartsEntry }) {
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["OpenSearch"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
splitKey: bodyshop?.imexshopid
|
||||
});
|
||||
// TODO - Client Update - Technically key is not doing anything here
|
||||
|
||||
return (
|
||||
<Row className="breadcrumb-container">
|
||||
<Col xs={24} sm={24} md={16}>
|
||||
@@ -35,7 +35,7 @@ export function BreadCrumbs({ breadcrumbs, bodyshop, isPartsEntry }) {
|
||||
key: "home",
|
||||
title: (
|
||||
<Link to={isPartsEntry ? `/parts/` : `/manage/`}>
|
||||
<HomeFilled /> {(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) || ""}
|
||||
<HomeFilled /> {(bodyshop?.shopname && `(${bodyshop.shopname})`) || ""}
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -4,13 +4,8 @@ import parsePhoneNumber from "libphonenumber-js";
|
||||
import { forwardRef } from "react";
|
||||
import "./phone-form-item.styles.scss";
|
||||
|
||||
function FormItemPhone(props) {
|
||||
return (
|
||||
<Input
|
||||
// country="ca" ref={ref} className="ant-input"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
function FormItemPhone(props, ref) {
|
||||
return <Input ref={ref} {...props} />;
|
||||
}
|
||||
|
||||
export default forwardRef(FormItemPhone);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AlertOutlined, BulbOutlined } from "@ant-design/icons";
|
||||
import { Button, Layout, Space } from "antd";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -17,18 +16,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
|
||||
export function GlobalFooter({ isPartsEntry }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
// Canny Not Required on Parts Entry
|
||||
if (isPartsEntry) return;
|
||||
window.Canny("initChangelog", {
|
||||
appID: "680bd2c7ee501290377f6686",
|
||||
position: "top",
|
||||
align: "left",
|
||||
theme: "light" // options: light [default], dark, auto
|
||||
});
|
||||
}, [isPartsEntry]);
|
||||
|
||||
|
||||
if (isPartsEntry) {
|
||||
return (
|
||||
<Footer>
|
||||
|
||||
@@ -8,19 +8,20 @@ import { createStructuredSelector } from "reselect";
|
||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectIsPartsEntry, selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly
|
||||
jobRO: selectJobReadOnly,
|
||||
isPartsEntry: selectIsPartsEntry
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
});
|
||||
|
||||
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
|
||||
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail, isPartsEntry }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [availableStatuses, setAvailableStatuses] = useState([]);
|
||||
@@ -45,25 +46,43 @@ export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
|
||||
});
|
||||
};
|
||||
|
||||
// Updates available statuses based on job and bodyshop context
|
||||
useEffect(() => {
|
||||
//Figure out what scenario were in, populate accodingly
|
||||
if (job && bodyshop) {
|
||||
if (bodyshop.md_ro_statuses.pre_production_statuses.includes(job.status)) {
|
||||
setAvailableStatuses(bodyshop.md_ro_statuses.pre_production_statuses);
|
||||
} else if (bodyshop.md_ro_statuses.production_statuses.includes(job.status)) {
|
||||
setAvailableStatuses(bodyshop.md_ro_statuses.production_statuses);
|
||||
} else if (bodyshop.md_ro_statuses.post_production_statuses.includes(job.status)) {
|
||||
setAvailableStatuses(
|
||||
bodyshop.md_ro_statuses.post_production_statuses.filter(
|
||||
(s) => s !== bodyshop.md_ro_statuses.default_invoiced && s !== bodyshop.md_ro_statuses.default_exported
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.log("Status didn't match any restrictions. Allowing all status changes.");
|
||||
setAvailableStatuses(bodyshop.md_ro_statuses.statuses);
|
||||
}
|
||||
if (!job || !bodyshop) return;
|
||||
|
||||
const { md_ro_statuses } = bodyshop;
|
||||
const {
|
||||
parts_statuses,
|
||||
pre_production_statuses,
|
||||
production_statuses,
|
||||
post_production_statuses,
|
||||
statuses,
|
||||
default_invoiced,
|
||||
default_exported
|
||||
} = md_ro_statuses;
|
||||
|
||||
if (isPartsEntry) {
|
||||
// Set parts-specific statuses for parts entry scenario
|
||||
setAvailableStatuses(parts_statuses);
|
||||
return;
|
||||
}
|
||||
}, [job, setAvailableStatuses, bodyshop]);
|
||||
|
||||
// Handle non-parts entry scenarios based on job status
|
||||
if (pre_production_statuses.includes(job.status)) {
|
||||
setAvailableStatuses(pre_production_statuses);
|
||||
} else if (production_statuses.includes(job.status)) {
|
||||
setAvailableStatuses(production_statuses);
|
||||
} else if (post_production_statuses.includes(job.status)) {
|
||||
// Filter out invoiced and exported statuses for post-production
|
||||
setAvailableStatuses(
|
||||
post_production_statuses.filter((status) => status !== default_invoiced && status !== default_exported)
|
||||
);
|
||||
} else {
|
||||
// Default to all statuses if no specific restrictions apply
|
||||
console.log("Status didn't match any restrictions. Allowing all status changes.");
|
||||
setAvailableStatuses(statuses);
|
||||
}
|
||||
}, [job, bodyshop, isPartsEntry, setAvailableStatuses]);
|
||||
|
||||
const statusMenu = {
|
||||
items: [
|
||||
|
||||
@@ -37,7 +37,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
|
||||
.filter(
|
||||
(temp) =>
|
||||
(!temp.regions ||
|
||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
||||
temp.regions?.[bodyshop.region_config] ||
|
||||
(temp.regions && bodyshop.region_config.includes(Object.keys(temp.regions)) === true)) &&
|
||||
(!temp.dms || temp.dms === false)
|
||||
)
|
||||
@@ -46,7 +46,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
|
||||
.filter(
|
||||
(temp) =>
|
||||
!temp.regions ||
|
||||
(temp.regions && temp.regions[bodyshop.region_config]) ||
|
||||
temp.regions?.[bodyshop.region_config] ||
|
||||
(temp.regions && bodyshop.region_config.includes(Object.keys(temp.regions)) === true)
|
||||
);
|
||||
|
||||
@@ -82,7 +82,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
|
||||
variables: { id: jobId }
|
||||
},
|
||||
{
|
||||
to: job && job.ownr_ea,
|
||||
to: job?.ownr_ea,
|
||||
subject: cards.find((c) => c.key === key)?.subject
|
||||
},
|
||||
"e",
|
||||
@@ -129,7 +129,7 @@ export function PrintCenterJobsPartsComponent({ printCenterModal, bodyshop, tech
|
||||
const columns = `repeat(${actions.length}, 1fr)`;
|
||||
|
||||
return (
|
||||
<Col key={item.key} xs={24} sm={12}>
|
||||
<Col key={item.key} xs={24} sm={24} md={24} lg={24} xl={24}>
|
||||
<Card hoverable style={{ minHeight: 100 }}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||
<div style={{ flex: "1 1 70%", minWidth: 0 }}>
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function ShopInfoContainer() {
|
||||
onFinish={handleFinish}
|
||||
initialValues={
|
||||
data
|
||||
? data.bodyshops[0].accountingconfig.ClosingPeriod
|
||||
? data?.bodyshops?.[0]?.accountingconfig?.ClosingPeriod
|
||||
? {
|
||||
...data.bodyshops[0],
|
||||
accountingconfig: {
|
||||
|
||||
@@ -96,13 +96,15 @@ export function SimplifiedPartsJobsListComponent({
|
||||
key: "status",
|
||||
|
||||
ellipsis: true,
|
||||
sorter: search?.search ? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses) : true,
|
||||
sorter: search?.search
|
||||
? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.parts_active_statuses)
|
||||
: true,
|
||||
sortOrder: sortcolumn === "status" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.status || t("general.labels.na");
|
||||
},
|
||||
filteredValue: filter?.status || null,
|
||||
filters: bodyshop.md_ro_statuses.statuses.map((s) => {
|
||||
filters: bodyshop.md_ro_statuses.parts_statuses.map((s) => {
|
||||
return { text: s, value: [s] };
|
||||
}),
|
||||
onFilter: (value, record) => value.includes(record.status)
|
||||
|
||||
@@ -167,7 +167,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
socketRef.current = socketInstance;
|
||||
|
||||
const handleBodyshopMessage = (message) => {
|
||||
if (!message || !message.type) return;
|
||||
if (!message?.type) return;
|
||||
switch (message.type) {
|
||||
case "alert-update":
|
||||
store.dispatch(addAlerts(message.payload));
|
||||
@@ -512,21 +512,20 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
||||
};
|
||||
|
||||
const unsubscribe = auth.onIdTokenChanged(async (user) => {
|
||||
if (user) {
|
||||
const token = await user.getIdToken();
|
||||
if (socketRef.current) {
|
||||
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
|
||||
} else {
|
||||
initializeSocket(token).catch((err) =>
|
||||
console.error(`Something went wrong Initializing Sockets: ${err?.message || ""}`)
|
||||
);
|
||||
}
|
||||
if (!user) {
|
||||
socketRef.current?.disconnect();
|
||||
socketRef.current = null;
|
||||
setIsConnected(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await user.getIdToken();
|
||||
if (socketRef.current) {
|
||||
socketRef.current.emit("update-token", { token, bodyshopId: bodyshop.id });
|
||||
} else {
|
||||
if (socketRef.current) {
|
||||
socketRef.current.disconnect();
|
||||
socketRef.current = null;
|
||||
setIsConnected(false);
|
||||
}
|
||||
initializeSocket(token).catch((err) =>
|
||||
console.error("Something went wrong Initializing Sockets:", err?.message || "")
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { getAuth, updatePassword, updateProfile } from "@firebase/auth";
|
||||
import { getFirestore } from "@firebase/firestore";
|
||||
import { getMessaging, getToken, onMessage } from "@firebase/messaging";
|
||||
import { store } from "../redux/store";
|
||||
import * as amplitude from '@amplitude/analytics-browser';
|
||||
import posthog from 'posthog-js'
|
||||
|
||||
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
|
||||
initializeApp(config);
|
||||
@@ -71,25 +73,33 @@ onMessage(messaging, (payload) => {
|
||||
});
|
||||
|
||||
export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
||||
const state = stateProp || store.getState();
|
||||
const eventParams = {
|
||||
shop: (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || null,
|
||||
user: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
|
||||
...additionalParams
|
||||
};
|
||||
// axios.post("/ioevent", {
|
||||
// useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
|
||||
// bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
|
||||
// operationName: eventName,
|
||||
// variables: additionalParams,
|
||||
// dbevent: false,
|
||||
// env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}`
|
||||
// });
|
||||
// console.log(
|
||||
// "%c[Analytics]",
|
||||
// "background-color: green ;font-weight:bold;",
|
||||
// eventName,
|
||||
// eventParams
|
||||
// );
|
||||
logEvent(analytics, eventName, eventParams);
|
||||
try {
|
||||
|
||||
const state = stateProp || store.getState();
|
||||
const eventParams = {
|
||||
shop: (state.user && state.user.bodyshop && state.user.bodyshop.shopname) || null,
|
||||
user: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
|
||||
...additionalParams
|
||||
};
|
||||
// axios.post("/ioevent", {
|
||||
// useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
|
||||
// bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
|
||||
// operationName: eventName,
|
||||
// variables: additionalParams,
|
||||
// dbevent: false,
|
||||
// env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}`
|
||||
// });
|
||||
// console.log(
|
||||
// "%c[Analytics]",
|
||||
// "background-color: green ;font-weight:bold;",
|
||||
// eventName,
|
||||
// eventParams
|
||||
// );
|
||||
logEvent(analytics, eventName, eventParams);
|
||||
amplitude.track(eventName, eventParams);
|
||||
posthog.capture(eventName, eventParams);
|
||||
|
||||
} finally {
|
||||
//If it fails, just keep going.
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,6 +14,8 @@ import { persistor, store } from "./redux/store";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
import "./translations/i18n";
|
||||
import "./utils/CleanAxios";
|
||||
import * as amplitude from "@amplitude/analytics-browser";
|
||||
import { PostHogProvider } from "posthog-js/react";
|
||||
|
||||
window.global ||= window;
|
||||
|
||||
@@ -23,10 +25,10 @@ registerSW({ immediate: true });
|
||||
// Dinero.globalLocale = "en-CA";
|
||||
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||
|
||||
amplitude.init("6228a598e57cd66875cfd41604f1f891", {});
|
||||
const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV6(createBrowserRouter);
|
||||
|
||||
const router = sentryCreateBrowserRouter(createRoutesFromElements(<Route path="*" element={<AppContainer />} />));
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
let styles =
|
||||
"font-weight: bold; font-size: 50px;color: red; 6px 6px 0 rgb(226,91,14) , 9px 9px 0 rgb(245,221,8) , 12px 12px 0 rgb(5,148,68) ";
|
||||
@@ -37,7 +39,12 @@ function App() {
|
||||
return (
|
||||
<PersistGate loading={<LoadingSpinner message="Restoring your settings..." />} persistor={persistor}>
|
||||
<Provider store={store}>
|
||||
<RouterProvider router={router} />
|
||||
<PostHogProvider
|
||||
apiKey={import.meta.env.VITE_PUBLIC_POSTHOG_KEY}
|
||||
options={{ autocapture: false, capture_exceptions: true }}
|
||||
>
|
||||
<RouterProvider router={router} />
|
||||
</PostHogProvider>
|
||||
</Provider>
|
||||
</PersistGate>
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FloatButton, Layout, Spin } from "antd";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
|
||||
// import preval from "preval.macro";
|
||||
import { lazy, Suspense, useEffect, useState } from "react";
|
||||
import { lazy, Suspense, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -23,6 +23,7 @@ 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 useAlertsNotifications from "../../hooks/useAlertsNotifications.jsx";
|
||||
import { selectDarkMode } from "../../redux/application/application.selectors.js";
|
||||
|
||||
const PrintCenterModalContainer = lazy(
|
||||
() => import("../../components/print-center-modal/print-center-modal.container")
|
||||
@@ -107,22 +108,26 @@ const { Content } = Layout;
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
conflict: selectInstanceConflict,
|
||||
bodyshop: selectBodyshop,
|
||||
partsManagementOnly: selectPartsManagementOnly
|
||||
partsManagementOnly: selectPartsManagementOnly,
|
||||
isDarkMode: selectDarkMode
|
||||
});
|
||||
|
||||
export function Manage({ conflict, bodyshop, partsManagementOnly }) {
|
||||
export function Manage({ conflict, bodyshop, partsManagementOnly, isDarkMode }) {
|
||||
const { t } = useTranslation();
|
||||
const [chatVisible] = useState(false);
|
||||
const didMount = useRef(false);
|
||||
|
||||
// Centralized alerts handling (fetch + dedupe + notifications)
|
||||
useAlertsNotifications();
|
||||
|
||||
useEffect(() => {
|
||||
if (didMount.current) return; // prevents dev StrictMode double-run
|
||||
didMount.current = true;
|
||||
window.Canny("initChangelog", {
|
||||
appID: "680bd2c7ee501290377f6686",
|
||||
position: "top",
|
||||
align: "left",
|
||||
theme: "light" // options: light [default], dark, auto
|
||||
theme: !isDarkMode ? "light" : "dark"
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -60,19 +60,19 @@ function SimplifiedPartsJobsDetailContainer({ setBreadcrumbs, addRecentItem, set
|
||||
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: "/parts/", label: t("titles.bc.jobs") },
|
||||
{ link: "/parts", label: t("titles.bc.parts") },
|
||||
{
|
||||
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")
|
||||
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(
|
||||
|
||||
@@ -22,7 +22,7 @@ export function SimplifiedPartsJobsPage({ setBreadcrumbs, setSelectedHeader }) {
|
||||
})
|
||||
});
|
||||
setSelectedHeader("parts-queue");
|
||||
setBreadcrumbs([{ link: "/parts", label: t("titles.bc.simplified-parts-jobs") }]);
|
||||
setBreadcrumbs([{ link: "/parts", label: t("titles.bc.parts") }]);
|
||||
}, [setBreadcrumbs, t, setSelectedHeader]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 { Navigate, Route, Routes, useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component.jsx";
|
||||
import ConflictComponent from "../../components/conflict/conflict.component.jsx";
|
||||
@@ -39,6 +39,15 @@ const mapStateToProps = createStructuredSelector({
|
||||
export function SimplifiedPartsPage({ conflict, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Redirector to strip '/parts/jobs' from path for non-detail routes
|
||||
function JobsStripRedirect() {
|
||||
// lazy import to avoid top-level import churn
|
||||
const location = useLocation();
|
||||
const { pathname, search, hash } = location;
|
||||
const target = pathname.replace("/parts/jobs", "/parts") + (search || "") + (hash || "");
|
||||
return <Navigate to={target} replace />;
|
||||
}
|
||||
|
||||
// Centralized alerts handling (fetch + dedupe + notifications)
|
||||
useAlertsNotifications();
|
||||
|
||||
@@ -67,6 +76,10 @@ export function SimplifiedPartsPage({ conflict, bodyshop }) {
|
||||
<EmailOverlayContainer />
|
||||
<PrintCenterModalContainer />
|
||||
<Routes>
|
||||
{/* Redirect legacy or relative routes that include '/jobs' segment */}
|
||||
<Route path="jobs" element={<JobsStripRedirect />} />
|
||||
<Route path="jobs/*" element={<JobsStripRedirect />} />
|
||||
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
|
||||
@@ -42,33 +42,31 @@ export function VehicleDetailContainer({ setBreadcrumbs, addRecentItem, setSelec
|
||||
imex: "$t(titles.imexonline)",
|
||||
rome: "$t(titles.romeonline)"
|
||||
}),
|
||||
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) || ""}`
|
||||
: ""
|
||||
vehicle: data?.vehicles_by_pk
|
||||
? `${data.vehicles_by_pk?.v_model_yr || ""} ${
|
||||
data.vehicles_by_pk?.v_make_desc || ""
|
||||
} ${data.vehicles_by_pk?.v_model_desc || ""}`
|
||||
: ""
|
||||
});
|
||||
setSelectedHeader("vehicles");
|
||||
|
||||
const crumbs = [];
|
||||
if (isPartsEntry) crumbs.push({ link: "/parts/", label: t("titles.bc.jobs") });
|
||||
if (isPartsEntry) crumbs.push({ link: "/parts", label: t("titles.bc.parts") });
|
||||
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) || ""}`
|
||||
: ""
|
||||
vehicle: data?.vehicles_by_pk
|
||||
? `${data.vehicles_by_pk?.v_model_yr || ""} ${
|
||||
data.vehicles_by_pk?.v_make_desc || ""
|
||||
} ${data.vehicles_by_pk?.v_model_desc || ""}`
|
||||
: ""
|
||||
})
|
||||
});
|
||||
|
||||
setBreadcrumbs(crumbs);
|
||||
|
||||
if (data && data.vehicles_by_pk)
|
||||
if (data?.vehicles_by_pk)
|
||||
addRecentItem(
|
||||
CreateRecentItem(
|
||||
vehId,
|
||||
|
||||
@@ -33,7 +33,7 @@ export function VehiclesPageContainer({ setBreadcrumbs, setSelectedHeader, isPar
|
||||
|
||||
if (isPartsEntry) {
|
||||
setBreadcrumbs([
|
||||
{ link: "/parts/", label: t("titles.bc.jobs") },
|
||||
{ link: "/parts", label: t("titles.bc.parts") },
|
||||
{ link: `${basePath}/vehicles`, label: t("titles.bc.vehicles") }
|
||||
]);
|
||||
} else {
|
||||
|
||||
@@ -49,6 +49,8 @@ import {
|
||||
validatePasswordResetSuccess
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
import * as amplitude from '@amplitude/analytics-browser';
|
||||
import posthog from 'posthog-js';
|
||||
|
||||
const fpPromise = FingerprintJS.load();
|
||||
|
||||
@@ -83,8 +85,6 @@ export function* onCheckUserSession() {
|
||||
|
||||
export function* isUserAuthenticated() {
|
||||
try {
|
||||
logImEXEvent("redux_auth_check");
|
||||
|
||||
const user = yield getCurrentUser();
|
||||
if (!user) {
|
||||
yield put(unauthorizedUser());
|
||||
@@ -92,6 +92,8 @@ export function* isUserAuthenticated() {
|
||||
}
|
||||
|
||||
LogRocket.identify(user.email);
|
||||
amplitude.setUserId(user.email);
|
||||
posthog.identify(user.email);
|
||||
|
||||
const eulaQuery = yield client.query({
|
||||
query: QUERY_EULA,
|
||||
@@ -137,6 +139,7 @@ export function* signOutStart() {
|
||||
imexshopid: state.user.bodyshop.imexshopid,
|
||||
type: "messaging"
|
||||
});
|
||||
amplitude.reset();
|
||||
} catch {
|
||||
console.log("No FCM token. Skipping unsubscribe.");
|
||||
}
|
||||
@@ -266,11 +269,11 @@ export function* signInSuccessSaga({ payload }) {
|
||||
instanceSeg,
|
||||
...(isParts
|
||||
? [
|
||||
InstanceRenderManager({
|
||||
imex: "ImexPartsManagement",
|
||||
rome: "RomePartsManagement"
|
||||
})
|
||||
]
|
||||
InstanceRenderManager({
|
||||
imex: "ImexPartsManagement",
|
||||
rome: "RomePartsManagement"
|
||||
})
|
||||
]
|
||||
: [])
|
||||
];
|
||||
window.$crisp.push(["set", "session:segments", [segs]]);
|
||||
@@ -295,7 +298,6 @@ export function* signInSuccessSaga({ payload }) {
|
||||
|
||||
setUserId(analytics, payload.email);
|
||||
setUserProperties(analytics, payload);
|
||||
yield logImEXEvent("redux_sign_in_success");
|
||||
}
|
||||
|
||||
export function* onSendPasswordResetStart() {
|
||||
@@ -362,6 +364,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
}
|
||||
|
||||
try {
|
||||
amplitude.setGroup('Shop', payload.shopname);
|
||||
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
||||
if (authRecord[0] && authRecord[0].user.validemail) {
|
||||
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
||||
|
||||
@@ -3523,6 +3523,8 @@
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"parts_settings": "Parts Management Settings | {{app}}",
|
||||
"simplified-parts-jobs": "Parts Management | {{app}}",
|
||||
"accounting-payables": "Payables | {{app}}",
|
||||
"accounting-payments": "Payments | {{app}}",
|
||||
"accounting-receivables": "Receivables | {{app}}",
|
||||
@@ -3530,7 +3532,7 @@
|
||||
"app": "",
|
||||
"bc": {
|
||||
"simplified-parts-jobs": "Jobs",
|
||||
"parts": "Jobs",
|
||||
"parts": "Parts",
|
||||
"parts_settings": "Settings",
|
||||
"accounting-payables": "Payables",
|
||||
"accounting-payments": "Payments",
|
||||
|
||||
@@ -3523,6 +3523,9 @@
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"simplified-parts-jobs": "",
|
||||
"parts": "",
|
||||
"parts_settings": "",
|
||||
"accounting-payables": "",
|
||||
"accounting-payments": "",
|
||||
"accounting-receivables": "",
|
||||
|
||||
@@ -3523,6 +3523,9 @@
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"simplified-parts-jobs": "",
|
||||
"parts": "",
|
||||
"parts_settings": "",
|
||||
"accounting-payables": "",
|
||||
"accounting-payments": "",
|
||||
"accounting-receivables": "",
|
||||
|
||||
Reference in New Issue
Block a user