feature/IO-3096-GlobalNotifications - Checkpoint - Header finalized, scenarioParser now uses ENV var for FILTER_SELF from watchers.
This commit is contained in:
@@ -1,4 +1,16 @@
|
|||||||
import Icon, {
|
import { Badge, Layout, Menu, Spin } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { useQuery } from "@apollo/client";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/socketContext.jsx";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import NotificationCenterContainer from "../notification-center/notification-center.container.jsx";
|
||||||
|
import LockWrapper from "../lock-wrapper/lock-wrapper.component";
|
||||||
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
|
import {
|
||||||
BankFilled,
|
BankFilled,
|
||||||
BarChartOutlined,
|
BarChartOutlined,
|
||||||
BellFilled,
|
BellFilled,
|
||||||
@@ -26,35 +38,21 @@ import Icon, {
|
|||||||
UnorderedListOutlined,
|
UnorderedListOutlined,
|
||||||
UserOutlined
|
UserOutlined
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
import { Badge, Layout, Menu, Space, Spin } from "antd";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { BsKanban } from "react-icons/bs";
|
import { BsKanban } from "react-icons/bs";
|
||||||
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
||||||
import { FiLogOut } from "react-icons/fi";
|
import { FiLogOut } from "react-icons/fi";
|
||||||
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
|
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
|
||||||
import { IoBusinessOutline } from "react-icons/io5";
|
import { IoBusinessOutline } from "react-icons/io5";
|
||||||
import { RiSurveyLine } from "react-icons/ri";
|
import { RiSurveyLine } from "react-icons/ri";
|
||||||
import { connect } from "react-redux";
|
import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
|
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { signOutStart } from "../../redux/user/user.actions";
|
import { signOutStart } from "../../redux/user/user.actions";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import day from "../../utils/day.js";
|
||||||
import LockWrapper from "../lock-wrapper/lock-wrapper.component";
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { debounce } from "lodash";
|
|
||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js";
|
|
||||||
import NotificationCenterContainer from "../notification-center/notification-center.container.jsx";
|
|
||||||
import { useSocket } from "../../contexts/SocketIO/socketContext.jsx";
|
|
||||||
|
|
||||||
// Used to Determine if the Header is in Mobile Mode, and to toggle the multiple menus
|
|
||||||
const HEADER_MOBILE_BREAKPOINT = 576;
|
|
||||||
|
|
||||||
|
// Redux mappings
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
recentItems: selectRecentItems,
|
recentItems: selectRecentItems,
|
||||||
@@ -63,43 +61,13 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setBillEnterContext: (context) =>
|
setBillEnterContext: (context) => dispatch(setModalContext({ context, modal: "billEnter" })),
|
||||||
dispatch(
|
setTimeTicketContext: (context) => dispatch(setModalContext({ context, modal: "timeTicket" })),
|
||||||
setModalContext({
|
setPaymentContext: (context) => dispatch(setModalContext({ context, modal: "payment" })),
|
||||||
context: context,
|
setReportCenterContext: (context) => dispatch(setModalContext({ context, modal: "reportCenter" })),
|
||||||
modal: "billEnter"
|
|
||||||
})
|
|
||||||
),
|
|
||||||
setTimeTicketContext: (context) =>
|
|
||||||
dispatch(
|
|
||||||
setModalContext({
|
|
||||||
context: context,
|
|
||||||
modal: "timeTicket"
|
|
||||||
})
|
|
||||||
),
|
|
||||||
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
|
|
||||||
setReportCenterContext: (context) =>
|
|
||||||
dispatch(
|
|
||||||
setModalContext({
|
|
||||||
context: context,
|
|
||||||
modal: "reportCenter"
|
|
||||||
})
|
|
||||||
),
|
|
||||||
signOutStart: () => dispatch(signOutStart()),
|
signOutStart: () => dispatch(signOutStart()),
|
||||||
setCardPaymentContext: (context) =>
|
setCardPaymentContext: (context) => dispatch(setModalContext({ context, modal: "cardPayment" })),
|
||||||
dispatch(
|
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||||
setModalContext({
|
|
||||||
context: context,
|
|
||||||
modal: "cardPayment"
|
|
||||||
})
|
|
||||||
),
|
|
||||||
setTaskUpsertContext: (context) =>
|
|
||||||
dispatch(
|
|
||||||
setModalContext({
|
|
||||||
context: context,
|
|
||||||
modal: "taskUpsert"
|
|
||||||
})
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function Header({
|
function Header({
|
||||||
@@ -125,12 +93,10 @@ function Header({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { isConnected } = useSocket();
|
const { isConnected } = useSocket();
|
||||||
const [notificationVisible, setNotificationVisible] = useState(false);
|
const [notificationVisible, setNotificationVisible] = useState(false);
|
||||||
|
|
||||||
const userAssociationId = bodyshop?.associations?.[0]?.id;
|
const userAssociationId = bodyshop?.associations?.[0]?.id;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: unreadData,
|
data: unreadData,
|
||||||
refetch: refetchUnread,
|
refetch: refetchUnread,
|
||||||
@@ -138,22 +104,21 @@ function Header({
|
|||||||
} = useQuery(GET_UNREAD_COUNT, {
|
} = useQuery(GET_UNREAD_COUNT, {
|
||||||
variables: { associationid: userAssociationId },
|
variables: { associationid: userAssociationId },
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
pollInterval: isConnected ? 0 : 30000, // Poll only if socket is down
|
pollInterval: isConnected ? 0 : day.duration(60, "seconds").asMilliseconds(),
|
||||||
skip: !userAssociationId // Skip query if no userAssociationId
|
skip: !userAssociationId
|
||||||
});
|
});
|
||||||
|
|
||||||
const unreadCount = unreadData?.notifications_aggregate?.aggregate?.count ?? 0;
|
const unreadCount = unreadData?.notifications_aggregate?.aggregate?.count ?? 0;
|
||||||
|
|
||||||
// Initial fetch and socket status handling
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userAssociationId) {
|
if (userAssociationId) {
|
||||||
refetchUnread().catch((e) => console.error(`Something went wrong fetching unread notifications: ${e?.message}`));
|
refetchUnread().catch((e) => console.error(`Error fetching unread notifications: ${e?.message}`));
|
||||||
}
|
}
|
||||||
}, [refetchUnread, userAssociationId]);
|
}, [refetchUnread, userAssociationId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isConnected && !unreadLoading && userAssociationId) {
|
if (!isConnected && !unreadLoading && userAssociationId) {
|
||||||
refetchUnread().catch((e) => console.error(`Something went wrong fetching unread notifications: ${e?.message}`));
|
refetchUnread().catch((e) => console.error(`Error fetching unread notifications: ${e?.message}`));
|
||||||
}
|
}
|
||||||
}, [isConnected, unreadLoading, refetchUnread, userAssociationId]);
|
}, [isConnected, unreadLoading, refetchUnread, userAssociationId]);
|
||||||
|
|
||||||
@@ -162,33 +127,11 @@ function Header({
|
|||||||
if (handleMenuClick) handleMenuClick(e);
|
if (handleMenuClick) handleMenuClick(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [isMobile, setIsMobile] = useState(() => {
|
const accountingChildren = [
|
||||||
const effectiveWidth = window.innerWidth / (window.devicePixelRatio || 1);
|
|
||||||
return effectiveWidth <= HEADER_MOBILE_BREAKPOINT;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleResize = debounce(() => {
|
|
||||||
const effectiveWidth = window.innerWidth / (window.devicePixelRatio || 1);
|
|
||||||
setIsMobile(effectiveWidth <= HEADER_MOBILE_BREAKPOINT);
|
|
||||||
}, 200);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
window.addEventListener("orientationchange", handleResize);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("resize", handleResize);
|
|
||||||
window.removeEventListener("orientationchange", handleResize);
|
|
||||||
handleResize.cancel(); // Cancel any pending debounced calls on cleanup
|
|
||||||
};
|
|
||||||
}, [handleResize]);
|
|
||||||
|
|
||||||
// Accounting children setup (unchanged)
|
|
||||||
const accountingChildren = [];
|
|
||||||
accountingChildren.push(
|
|
||||||
{
|
{
|
||||||
key: "bills",
|
key: "bills",
|
||||||
id: "header-accounting-bills",
|
id: "header-accounting-bills",
|
||||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
icon: <FaFileInvoiceDollar />,
|
||||||
label: (
|
label: (
|
||||||
<Link to="/manage/bills">
|
<Link to="/manage/bills">
|
||||||
<LockWrapper featureName="bills" bodyshop={bodyshop}>
|
<LockWrapper featureName="bills" bodyshop={bodyshop}>
|
||||||
@@ -200,42 +143,31 @@ function Header({
|
|||||||
{
|
{
|
||||||
key: "enterbills",
|
key: "enterbills",
|
||||||
id: "header-accounting-enterbills",
|
id: "header-accounting-enterbills",
|
||||||
icon: <Icon component={GiPayMoney} />,
|
icon: <GiPayMoney />,
|
||||||
label: (
|
label: (
|
||||||
<Space>
|
<LockWrapper featureName="bills" bodyshop={bodyshop}>
|
||||||
<LockWrapper featureName="bills" bodyshop={bodyshop}>
|
{t("menus.header.enterbills")}
|
||||||
{t("menus.header.enterbills")}
|
</LockWrapper>
|
||||||
</LockWrapper>
|
|
||||||
</Space>
|
|
||||||
),
|
),
|
||||||
onClick: () => {
|
onClick: () =>
|
||||||
HasFeatureAccess({ featureName: "bills", bodyshop }) &&
|
HasFeatureAccess({ featureName: "bills", bodyshop }) &&
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: {}
|
context: {}
|
||||||
});
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (Simple_Inventory.treatment === "on") {
|
|
||||||
accountingChildren.push(
|
|
||||||
{
|
|
||||||
type: "divider"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "inventory",
|
|
||||||
id: "header-accounting-inventory",
|
|
||||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
|
||||||
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
accountingChildren.push(
|
|
||||||
{
|
|
||||||
type: "divider"
|
|
||||||
},
|
},
|
||||||
|
...(Simple_Inventory.treatment === "on"
|
||||||
|
? [
|
||||||
|
{ type: "divider" },
|
||||||
|
{
|
||||||
|
key: "inventory",
|
||||||
|
id: "header-accounting-inventory",
|
||||||
|
icon: <FaFileInvoiceDollar />,
|
||||||
|
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{ type: "divider" },
|
||||||
{
|
{
|
||||||
key: "allpayments",
|
key: "allpayments",
|
||||||
id: "header-accounting-allpayments",
|
id: "header-accounting-allpayments",
|
||||||
@@ -251,41 +183,31 @@ function Header({
|
|||||||
{
|
{
|
||||||
key: "enterpayments",
|
key: "enterpayments",
|
||||||
id: "header-accounting-enterpayments",
|
id: "header-accounting-enterpayments",
|
||||||
icon: <Icon component={FaCreditCard} />,
|
icon: <FaCreditCard />,
|
||||||
label: (
|
label: (
|
||||||
<LockWrapper featureName="payments" bodyshop={bodyshop}>
|
<LockWrapper featureName="payments" bodyshop={bodyshop}>
|
||||||
{t("menus.header.enterpayment")}
|
{t("menus.header.enterpayment")}
|
||||||
</LockWrapper>
|
</LockWrapper>
|
||||||
),
|
),
|
||||||
onClick: () => {
|
onClick: () =>
|
||||||
HasFeatureAccess({ featureName: "payments", bodyshop }) &&
|
HasFeatureAccess({ featureName: "payments", bodyshop }) &&
|
||||||
setPaymentContext({
|
setPaymentContext({
|
||||||
actions: {},
|
|
||||||
context: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ImEXPay.treatment === "on") {
|
|
||||||
accountingChildren.push({
|
|
||||||
key: "entercardpayments",
|
|
||||||
id: "header-accounting-entercardpayments",
|
|
||||||
icon: <Icon component={FaCreditCard} />,
|
|
||||||
label: t("menus.header.entercardpayment"),
|
|
||||||
onClick: () => {
|
|
||||||
setCardPaymentContext({
|
|
||||||
actions: {},
|
actions: {},
|
||||||
context: {}
|
context: null
|
||||||
});
|
})
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
accountingChildren.push(
|
|
||||||
{
|
|
||||||
type: "divider"
|
|
||||||
},
|
},
|
||||||
|
...(ImEXPay.treatment === "on"
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: "entercardpayments",
|
||||||
|
id: "header-accounting-entercardpayments",
|
||||||
|
icon: <FaCreditCard />,
|
||||||
|
label: t("menus.header.entercardpayment"),
|
||||||
|
onClick: () => setCardPaymentContext({ actions: {}, context: {} })
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{ type: "divider" },
|
||||||
{
|
{
|
||||||
key: "timetickets",
|
key: "timetickets",
|
||||||
id: "header-accounting-timetickets",
|
id: "header-accounting-timetickets",
|
||||||
@@ -297,133 +219,124 @@ function Header({
|
|||||||
</LockWrapper>
|
</LockWrapper>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
);
|
...(bodyshop?.md_tasks_presets?.use_approvals
|
||||||
|
? [
|
||||||
if (bodyshop?.md_tasks_presets?.use_approvals) {
|
{
|
||||||
accountingChildren.push({
|
key: "ttapprovals",
|
||||||
key: "ttapprovals",
|
id: "header-accounting-ttapprovals",
|
||||||
id: "header-accounting-ttapprovals",
|
icon: <FieldTimeOutlined />,
|
||||||
icon: <FieldTimeOutlined />,
|
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
||||||
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
}
|
||||||
});
|
]
|
||||||
}
|
: []),
|
||||||
accountingChildren.push(
|
|
||||||
{
|
{
|
||||||
key: "entertimetickets",
|
key: "entertimetickets",
|
||||||
icon: <Icon component={GiPlayerTime} />,
|
id: "header-accounting-entertimetickets",
|
||||||
|
icon: <GiPlayerTime />,
|
||||||
label: (
|
label: (
|
||||||
<LockWrapper featureName="timetickets" bodyshop={bodyshop}>
|
<LockWrapper featureName="timetickets" bodyshop={bodyshop}>
|
||||||
{t("menus.header.entertimeticket")}
|
{t("menus.header.entertimeticket")}
|
||||||
</LockWrapper>
|
</LockWrapper>
|
||||||
),
|
),
|
||||||
id: "header-accounting-entertimetickets",
|
onClick: () =>
|
||||||
onClick: () => {
|
|
||||||
HasFeatureAccess({ featureName: "timetickets", bodyshop }) &&
|
HasFeatureAccess({ featureName: "timetickets", bodyshop }) &&
|
||||||
setTimeTicketContext({
|
setTimeTicketContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: {
|
context: {
|
||||||
created_by: currentUser.displayName
|
created_by: currentUser.displayName
|
||||||
? currentUser.email.concat(" | ", currentUser.displayName)
|
? `${currentUser.email} | ${currentUser.displayName}`
|
||||||
: currentUser.email
|
: currentUser.email
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
{ type: "divider" },
|
||||||
{
|
{
|
||||||
type: "divider"
|
key: "accountingexport",
|
||||||
}
|
id: "header-accounting-export",
|
||||||
);
|
icon: <ExportOutlined />,
|
||||||
|
|
||||||
const accountingExportChildren = [
|
|
||||||
{
|
|
||||||
key: "receivables",
|
|
||||||
id: "header-accounting-receivables",
|
|
||||||
label: (
|
label: (
|
||||||
<Link to="/manage/accounting/receivables">
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
{t("menus.header.export")}
|
||||||
{t("menus.header.accounting-receivables")}
|
</LockWrapper>
|
||||||
</LockWrapper>
|
),
|
||||||
</Link>
|
children: [
|
||||||
)
|
{
|
||||||
|
key: "receivables",
|
||||||
|
id: "header-accounting-receivables",
|
||||||
|
label: (
|
||||||
|
<Link to="/manage/accounting/receivables">
|
||||||
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.accounting-receivables")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
...(!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) ||
|
||||||
|
DmsAp.treatment === "on"
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: "payables",
|
||||||
|
id: "header-accounting-payables",
|
||||||
|
label: (
|
||||||
|
<Link to="/manage/accounting/payables">
|
||||||
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.accounting-payables")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber))
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: "payments",
|
||||||
|
id: "header-accounting-payments",
|
||||||
|
label: (
|
||||||
|
<Link to="/manage/accounting/payments">
|
||||||
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.accounting-payments")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
{ type: "divider" },
|
||||||
|
{
|
||||||
|
key: "exportlogs",
|
||||||
|
id: "header-accounting-exportlogs",
|
||||||
|
label: (
|
||||||
|
<Link to="/manage/accounting/exportlogs">
|
||||||
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
|
{t("menus.header.export-logs")}
|
||||||
|
</LockWrapper>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || DmsAp.treatment === "on") {
|
// Left menu items (includes original navigation items)
|
||||||
accountingExportChildren.push({
|
const leftMenuItems = [
|
||||||
key: "payables",
|
|
||||||
id: "header-accounting-payables",
|
|
||||||
label: (
|
|
||||||
<Link to="/manage/accounting/payables">
|
|
||||||
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
|
||||||
{t("menus.header.accounting-payables")}
|
|
||||||
</LockWrapper>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber))) {
|
|
||||||
accountingExportChildren.push({
|
|
||||||
key: "payments",
|
|
||||||
id: "header-accounting-payments",
|
|
||||||
label: (
|
|
||||||
<Link to="/manage/accounting/payments">
|
|
||||||
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
|
||||||
{t("menus.header.accounting-payments")}
|
|
||||||
</LockWrapper>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
accountingExportChildren.push(
|
|
||||||
{
|
|
||||||
type: "divider"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "exportlogs",
|
|
||||||
id: "header-accounting-exportlogs",
|
|
||||||
label: (
|
|
||||||
<Link to="/manage/accounting/exportlogs">
|
|
||||||
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
|
||||||
{t("menus.header.export-logs")}
|
|
||||||
</LockWrapper>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
accountingChildren.push({
|
|
||||||
key: "accountingexport",
|
|
||||||
id: "header-accounting-export",
|
|
||||||
icon: <ExportOutlined />,
|
|
||||||
label: (
|
|
||||||
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
|
||||||
{t("menus.header.export")}
|
|
||||||
</LockWrapper>
|
|
||||||
),
|
|
||||||
children: accountingExportChildren
|
|
||||||
});
|
|
||||||
|
|
||||||
// Define all menu items
|
|
||||||
const menuItems = [
|
|
||||||
{
|
{
|
||||||
key: "home",
|
key: "home",
|
||||||
icon: <HomeFilled />,
|
|
||||||
id: "header-home",
|
id: "header-home",
|
||||||
|
icon: <HomeFilled />,
|
||||||
label: <Link to="/manage/">{t("menus.header.home")}</Link>
|
label: <Link to="/manage/">{t("menus.header.home")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "schedule",
|
key: "schedule",
|
||||||
id: "header-schedule",
|
id: "header-schedule",
|
||||||
icon: <Icon component={FaCalendarAlt} />,
|
icon: <FaCalendarAlt />,
|
||||||
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
|
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "jobssubmenu",
|
key: "jobssubmenu",
|
||||||
id: "header-jobs",
|
id: "header-jobs",
|
||||||
icon: <Icon component={FaCarCrash} />,
|
icon: <FaCarCrash />,
|
||||||
label: t("menus.header.jobs"),
|
label: t("menus.header.jobs"),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -456,20 +369,14 @@ function Header({
|
|||||||
icon: <FileAddOutlined />,
|
icon: <FileAddOutlined />,
|
||||||
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
|
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{ type: "divider" },
|
||||||
type: "divider",
|
|
||||||
id: "header-jobs-divider"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "alljobs",
|
key: "alljobs",
|
||||||
id: "header-all-jobs",
|
id: "header-all-jobs",
|
||||||
icon: <UnorderedListOutlined />,
|
icon: <UnorderedListOutlined />,
|
||||||
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
|
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{ type: "divider" },
|
||||||
type: "divider",
|
|
||||||
id: "header-jobs-divider2"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "productionlist",
|
key: "productionlist",
|
||||||
id: "header-production-list",
|
id: "header-production-list",
|
||||||
@@ -479,7 +386,7 @@ function Header({
|
|||||||
{
|
{
|
||||||
key: "productionboard",
|
key: "productionboard",
|
||||||
id: "header-production-board",
|
id: "header-production-board",
|
||||||
icon: <Icon component={BsKanban} />,
|
icon: <BsKanban />,
|
||||||
label: (
|
label: (
|
||||||
<Link to="/manage/production/board">
|
<Link to="/manage/production/board">
|
||||||
<LockWrapper featureName="visualboard" bodyshop={bodyshop}>
|
<LockWrapper featureName="visualboard" bodyshop={bodyshop}>
|
||||||
@@ -488,10 +395,7 @@ function Header({
|
|||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{ type: "divider" },
|
||||||
type: "divider",
|
|
||||||
id: "header-jobs-divider3"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "scoreboard",
|
key: "scoreboard",
|
||||||
id: "header-scoreboard",
|
id: "header-scoreboard",
|
||||||
@@ -508,8 +412,8 @@ function Header({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "customers",
|
key: "customers",
|
||||||
icon: <UserOutlined />,
|
|
||||||
id: "header-customers",
|
id: "header-customers",
|
||||||
|
icon: <UserOutlined />,
|
||||||
label: t("menus.header.customers"),
|
label: t("menus.header.customers"),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -614,12 +518,7 @@ function Header({
|
|||||||
id: "header-create-task",
|
id: "header-create-task",
|
||||||
icon: <PlusCircleOutlined />,
|
icon: <PlusCircleOutlined />,
|
||||||
label: t("menus.header.create_task"),
|
label: t("menus.header.create_task"),
|
||||||
onClick: () => {
|
onClick: () => setTaskUpsertContext({ actions: {}, context: {} })
|
||||||
setTaskUpsertContext({
|
|
||||||
actions: {},
|
|
||||||
context: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "mytasks",
|
key: "mytasks",
|
||||||
@@ -644,7 +543,7 @@ function Header({
|
|||||||
{
|
{
|
||||||
key: "shop",
|
key: "shop",
|
||||||
id: "header-shop",
|
id: "header-shop",
|
||||||
icon: <Icon component={GiSettingsKnobs} />,
|
icon: <GiSettingsKnobs />,
|
||||||
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
|
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -662,23 +561,18 @@ function Header({
|
|||||||
id: "header-reportcenter",
|
id: "header-reportcenter",
|
||||||
icon: <BarChartOutlined />,
|
icon: <BarChartOutlined />,
|
||||||
label: t("menus.header.reportcenter"),
|
label: t("menus.header.reportcenter"),
|
||||||
onClick: () => {
|
onClick: () => setReportCenterContext({ actions: {}, context: {} })
|
||||||
setReportCenterContext({
|
|
||||||
actions: {},
|
|
||||||
context: {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "shop-vendors",
|
key: "shop-vendors",
|
||||||
id: "header-shop-vendors",
|
id: "header-shop-vendors",
|
||||||
icon: <Icon component={IoBusinessOutline} />,
|
icon: <IoBusinessOutline />,
|
||||||
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
|
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "shop-csi",
|
key: "shop-csi",
|
||||||
id: "header-shop-csi",
|
id: "header-shop-csi",
|
||||||
icon: <Icon component={RiSurveyLine} />,
|
icon: <RiSurveyLine />,
|
||||||
label: (
|
label: (
|
||||||
<Link to="/manage/shop/csi">
|
<Link to="/manage/shop/csi">
|
||||||
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
@@ -689,23 +583,10 @@ function Header({
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// Right-aligned items on desktop, merged on mobile
|
|
||||||
{
|
|
||||||
key: "notifications",
|
|
||||||
icon: unreadLoading ? (
|
|
||||||
<Spin size="small" />
|
|
||||||
) : (
|
|
||||||
<Badge count={unreadCount}>
|
|
||||||
<BellFilled />
|
|
||||||
</Badge>
|
|
||||||
),
|
|
||||||
id: "header-notifications",
|
|
||||||
onClick: handleNotificationClick
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: "recent",
|
key: "recent",
|
||||||
icon: <ClockCircleFilled />,
|
|
||||||
id: "header-recent",
|
id: "header-recent",
|
||||||
|
icon: <ClockCircleFilled />,
|
||||||
children: recentItems.map((i, idx) => ({
|
children: recentItems.map((i, idx) => ({
|
||||||
key: idx,
|
key: idx,
|
||||||
id: `header-recent-${idx}`,
|
id: `header-recent-${idx}`,
|
||||||
@@ -714,13 +595,13 @@ function Header({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "user",
|
key: "user",
|
||||||
|
id: "header-user",
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
// label: currentUser.displayName || currentUser.email || t("general.labels.unknown"),
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
key: "signout",
|
key: "signout",
|
||||||
id: "header-signout",
|
id: "header-signout",
|
||||||
icon: <Icon component={FiLogOut} />,
|
icon: <FiLogOut />,
|
||||||
danger: true,
|
danger: true,
|
||||||
label: t("user.actions.signout"),
|
label: t("user.actions.signout"),
|
||||||
onClick: () => signOutStart()
|
onClick: () => signOutStart()
|
||||||
@@ -728,32 +609,25 @@ function Header({
|
|||||||
{
|
{
|
||||||
key: "help",
|
key: "help",
|
||||||
id: "header-help",
|
id: "header-help",
|
||||||
icon: <Icon component={QuestionCircleFilled} />,
|
icon: <QuestionCircleFilled />,
|
||||||
label: t("menus.header.help"),
|
label: t("menus.header.help"),
|
||||||
onClick: () => {
|
onClick: () => window.open("https://help.imex.online/", "_blank")
|
||||||
window.open("https://help.imex.online/", "_blank");
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
...(InstanceRenderManager({
|
...(InstanceRenderManager({ imex: true, rome: false })
|
||||||
imex: true,
|
|
||||||
rome: false
|
|
||||||
})
|
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
key: "rescue",
|
key: "rescue",
|
||||||
id: "header-rescue",
|
id: "header-rescue",
|
||||||
icon: <Icon component={CarFilled} />,
|
icon: <CarFilled />,
|
||||||
label: t("menus.header.rescueme"),
|
label: t("menus.header.rescueme"),
|
||||||
onClick: () => {
|
onClick: () => window.open("https://imexrescue.com/", "_blank")
|
||||||
window.open("https://imexrescue.com/", "_blank");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
key: "shiftclock",
|
key: "shiftclock",
|
||||||
id: "header-shiftclock",
|
id: "header-shiftclock",
|
||||||
icon: <Icon component={GiPlayerTime} />,
|
icon: <GiPlayerTime />,
|
||||||
label: (
|
label: (
|
||||||
<Link to="/manage/shiftclock">
|
<Link to="/manage/shiftclock">
|
||||||
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||||
@@ -772,58 +646,59 @@ function Header({
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Notifications item (always on the right)
|
||||||
|
const notificationItem = [
|
||||||
|
{
|
||||||
|
key: "notifications",
|
||||||
|
id: "header-notifications",
|
||||||
|
icon: unreadLoading ? (
|
||||||
|
<Spin size="small" />
|
||||||
|
) : (
|
||||||
|
<Badge count={unreadCount}>
|
||||||
|
<BellFilled />
|
||||||
|
</Badge>
|
||||||
|
),
|
||||||
|
onClick: handleNotificationClick
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Header style={{ padding: 0 }}>
|
<Layout.Header style={{ padding: 0, background: "#001529" }}>
|
||||||
{isMobile ? (
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "100%",
|
||||||
|
overflow: "hidden"
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Menu
|
<Menu
|
||||||
mode="horizontal"
|
mode="horizontal"
|
||||||
theme="dark"
|
theme="dark"
|
||||||
selectedKeys={[selectedHeader]}
|
selectedKeys={[selectedHeader]}
|
||||||
onClick={handleMenuClick}
|
onClick={handleMenuClick}
|
||||||
subMenuCloseDelay={0.3}
|
subMenuCloseDelay={0.3}
|
||||||
items={menuItems}
|
items={leftMenuItems}
|
||||||
style={{ width: "100%" }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
flex: "1 1 auto",
|
||||||
justifyContent: "space-between",
|
minWidth: 0,
|
||||||
alignItems: "center",
|
overflowX: "auto",
|
||||||
width: "100%"
|
borderBottom: "none",
|
||||||
|
background: "transparent"
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<Menu
|
<Menu
|
||||||
mode="horizontal"
|
mode="horizontal"
|
||||||
theme="dark"
|
theme="dark"
|
||||||
selectedKeys={[selectedHeader]}
|
selectedKeys={[selectedHeader]}
|
||||||
onClick={handleMenuClick}
|
onClick={handleMenuClick}
|
||||||
subMenuCloseDelay={0.3}
|
subMenuCloseDelay={0.3}
|
||||||
items={menuItems.slice(0, -3)}
|
items={notificationItem}
|
||||||
style={{
|
style={{ flex: "0 0 auto", minWidth: 0, borderBottom: "none", background: "transparent" }}
|
||||||
flex: "0 1 auto",
|
/>
|
||||||
justifyContent: "flex-start",
|
</div>
|
||||||
minWidth: 0,
|
<NotificationCenterContainer visible={notificationVisible} onClose={() => setNotificationVisible(false)} />
|
||||||
overflow: "visible"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div style={{ flex: "1 0 0" }} />
|
|
||||||
<Menu
|
|
||||||
mode="horizontal"
|
|
||||||
theme="dark"
|
|
||||||
selectedKeys={[selectedHeader]}
|
|
||||||
onClick={handleMenuClick}
|
|
||||||
subMenuCloseDelay={0.3}
|
|
||||||
items={menuItems.slice(-3)}
|
|
||||||
style={{
|
|
||||||
flex: "0 0 auto",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
overflow: "visible"
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<NotificationCenterContainer visible={notificationVisible} onClose={() => setNotificationVisible(false)} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Layout.Header>
|
</Layout.Header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const { dispatchEmailsToQueue } = require("./queues/emailQueue");
|
|||||||
const { dispatchAppsToQueue } = require("./queues/appQueue");
|
const { dispatchAppsToQueue } = require("./queues/appQueue");
|
||||||
|
|
||||||
// If true, the user who commits the action will NOT receive notifications; if false, they will.
|
// If true, the user who commits the action will NOT receive notifications; if false, they will.
|
||||||
const FILTER_SELF_FROM_WATCHERS = (() => process.env.NODE_ENV === "production")();
|
const FILTER_SELF_FROM_WATCHERS = process.env?.FILTER_SELF_FROM_WATCHERS === "true";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an event and determines matching scenarios for notifications.
|
* Parses an event and determines matching scenarios for notifications.
|
||||||
|
|||||||
Reference in New Issue
Block a user