feature/IO-3096-GlobalNotifications - Checkpoint - Header finalized, scenarioParser now uses ENV var for FILTER_SELF from watchers.

This commit is contained in:
Dave Richer
2025-02-28 17:17:13 -05:00
parent a5904f55aa
commit f51fa08961
2 changed files with 221 additions and 346 deletions

View File

@@ -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>
); );
} }

View File

@@ -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.