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,
|
||||
BarChartOutlined,
|
||||
BellFilled,
|
||||
@@ -26,35 +38,21 @@ import Icon, {
|
||||
UnorderedListOutlined,
|
||||
UserOutlined
|
||||
} 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 { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
||||
import { FiLogOut } from "react-icons/fi";
|
||||
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
|
||||
import { IoBusinessOutline } from "react-icons/io5";
|
||||
import { RiSurveyLine } from "react-icons/ri";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js";
|
||||
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { signOutStart } from "../../redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
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;
|
||||
import day from "../../utils/day.js";
|
||||
|
||||
// Redux mappings
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
recentItems: selectRecentItems,
|
||||
@@ -63,43 +61,13 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
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"
|
||||
})
|
||||
),
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context, modal: "billEnter" })),
|
||||
setTimeTicketContext: (context) => dispatch(setModalContext({ context, modal: "timeTicket" })),
|
||||
setPaymentContext: (context) => dispatch(setModalContext({ context, modal: "payment" })),
|
||||
setReportCenterContext: (context) => dispatch(setModalContext({ context, modal: "reportCenter" })),
|
||||
signOutStart: () => dispatch(signOutStart()),
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "cardPayment"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "taskUpsert"
|
||||
})
|
||||
)
|
||||
setCardPaymentContext: (context) => dispatch(setModalContext({ context, modal: "cardPayment" })),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
function Header({
|
||||
@@ -125,12 +93,10 @@ function Header({
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { isConnected } = useSocket();
|
||||
const [notificationVisible, setNotificationVisible] = useState(false);
|
||||
|
||||
const userAssociationId = bodyshop?.associations?.[0]?.id;
|
||||
|
||||
const {
|
||||
data: unreadData,
|
||||
refetch: refetchUnread,
|
||||
@@ -138,22 +104,21 @@ function Header({
|
||||
} = useQuery(GET_UNREAD_COUNT, {
|
||||
variables: { associationid: userAssociationId },
|
||||
fetchPolicy: "network-only",
|
||||
pollInterval: isConnected ? 0 : 30000, // Poll only if socket is down
|
||||
skip: !userAssociationId // Skip query if no userAssociationId
|
||||
pollInterval: isConnected ? 0 : day.duration(60, "seconds").asMilliseconds(),
|
||||
skip: !userAssociationId
|
||||
});
|
||||
|
||||
const unreadCount = unreadData?.notifications_aggregate?.aggregate?.count ?? 0;
|
||||
|
||||
// Initial fetch and socket status handling
|
||||
useEffect(() => {
|
||||
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]);
|
||||
|
||||
useEffect(() => {
|
||||
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]);
|
||||
|
||||
@@ -162,33 +127,11 @@ function Header({
|
||||
if (handleMenuClick) handleMenuClick(e);
|
||||
};
|
||||
|
||||
const [isMobile, setIsMobile] = useState(() => {
|
||||
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(
|
||||
const accountingChildren = [
|
||||
{
|
||||
key: "bills",
|
||||
id: "header-accounting-bills",
|
||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
||||
icon: <FaFileInvoiceDollar />,
|
||||
label: (
|
||||
<Link to="/manage/bills">
|
||||
<LockWrapper featureName="bills" bodyshop={bodyshop}>
|
||||
@@ -200,42 +143,31 @@ function Header({
|
||||
{
|
||||
key: "enterbills",
|
||||
id: "header-accounting-enterbills",
|
||||
icon: <Icon component={GiPayMoney} />,
|
||||
icon: <GiPayMoney />,
|
||||
label: (
|
||||
<Space>
|
||||
<LockWrapper featureName="bills" bodyshop={bodyshop}>
|
||||
{t("menus.header.enterbills")}
|
||||
</LockWrapper>
|
||||
</Space>
|
||||
<LockWrapper featureName="bills" bodyshop={bodyshop}>
|
||||
{t("menus.header.enterbills")}
|
||||
</LockWrapper>
|
||||
),
|
||||
onClick: () => {
|
||||
onClick: () =>
|
||||
HasFeatureAccess({ featureName: "bills", bodyshop }) &&
|
||||
setBillEnterContext({
|
||||
actions: {},
|
||||
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"
|
||||
setBillEnterContext({
|
||||
actions: {},
|
||||
context: {}
|
||||
})
|
||||
},
|
||||
...(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",
|
||||
id: "header-accounting-allpayments",
|
||||
@@ -251,41 +183,31 @@ function Header({
|
||||
{
|
||||
key: "enterpayments",
|
||||
id: "header-accounting-enterpayments",
|
||||
icon: <Icon component={FaCreditCard} />,
|
||||
icon: <FaCreditCard />,
|
||||
label: (
|
||||
<LockWrapper featureName="payments" bodyshop={bodyshop}>
|
||||
{t("menus.header.enterpayment")}
|
||||
</LockWrapper>
|
||||
),
|
||||
onClick: () => {
|
||||
onClick: () =>
|
||||
HasFeatureAccess({ featureName: "payments", bodyshop }) &&
|
||||
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({
|
||||
setPaymentContext({
|
||||
actions: {},
|
||||
context: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
accountingChildren.push(
|
||||
{
|
||||
type: "divider"
|
||||
context: null
|
||||
})
|
||||
},
|
||||
...(ImEXPay.treatment === "on"
|
||||
? [
|
||||
{
|
||||
key: "entercardpayments",
|
||||
id: "header-accounting-entercardpayments",
|
||||
icon: <FaCreditCard />,
|
||||
label: t("menus.header.entercardpayment"),
|
||||
onClick: () => setCardPaymentContext({ actions: {}, context: {} })
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "timetickets",
|
||||
id: "header-accounting-timetickets",
|
||||
@@ -297,133 +219,124 @@ function Header({
|
||||
</LockWrapper>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
if (bodyshop?.md_tasks_presets?.use_approvals) {
|
||||
accountingChildren.push({
|
||||
key: "ttapprovals",
|
||||
id: "header-accounting-ttapprovals",
|
||||
icon: <FieldTimeOutlined />,
|
||||
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
||||
});
|
||||
}
|
||||
accountingChildren.push(
|
||||
},
|
||||
...(bodyshop?.md_tasks_presets?.use_approvals
|
||||
? [
|
||||
{
|
||||
key: "ttapprovals",
|
||||
id: "header-accounting-ttapprovals",
|
||||
icon: <FieldTimeOutlined />,
|
||||
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "entertimetickets",
|
||||
icon: <Icon component={GiPlayerTime} />,
|
||||
id: "header-accounting-entertimetickets",
|
||||
icon: <GiPlayerTime />,
|
||||
label: (
|
||||
<LockWrapper featureName="timetickets" bodyshop={bodyshop}>
|
||||
{t("menus.header.entertimeticket")}
|
||||
</LockWrapper>
|
||||
),
|
||||
id: "header-accounting-entertimetickets",
|
||||
onClick: () => {
|
||||
onClick: () =>
|
||||
HasFeatureAccess({ featureName: "timetickets", bodyshop }) &&
|
||||
setTimeTicketContext({
|
||||
actions: {},
|
||||
context: {
|
||||
created_by: currentUser.displayName
|
||||
? currentUser.email.concat(" | ", currentUser.displayName)
|
||||
: currentUser.email
|
||||
}
|
||||
});
|
||||
}
|
||||
setTimeTicketContext({
|
||||
actions: {},
|
||||
context: {
|
||||
created_by: currentUser.displayName
|
||||
? `${currentUser.email} | ${currentUser.displayName}`
|
||||
: currentUser.email
|
||||
}
|
||||
})
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
type: "divider"
|
||||
}
|
||||
);
|
||||
|
||||
const accountingExportChildren = [
|
||||
{
|
||||
key: "receivables",
|
||||
id: "header-accounting-receivables",
|
||||
key: "accountingexport",
|
||||
id: "header-accounting-export",
|
||||
icon: <ExportOutlined />,
|
||||
label: (
|
||||
<Link to="/manage/accounting/receivables">
|
||||
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||
{t("menus.header.accounting-receivables")}
|
||||
</LockWrapper>
|
||||
</Link>
|
||||
)
|
||||
<LockWrapper featureName="export" bodyshop={bodyshop}>
|
||||
{t("menus.header.export")}
|
||||
</LockWrapper>
|
||||
),
|
||||
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") {
|
||||
accountingExportChildren.push({
|
||||
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 = [
|
||||
// Left menu items (includes original navigation items)
|
||||
const leftMenuItems = [
|
||||
{
|
||||
key: "home",
|
||||
icon: <HomeFilled />,
|
||||
id: "header-home",
|
||||
icon: <HomeFilled />,
|
||||
label: <Link to="/manage/">{t("menus.header.home")}</Link>
|
||||
},
|
||||
{
|
||||
key: "schedule",
|
||||
id: "header-schedule",
|
||||
icon: <Icon component={FaCalendarAlt} />,
|
||||
icon: <FaCalendarAlt />,
|
||||
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
|
||||
},
|
||||
{
|
||||
key: "jobssubmenu",
|
||||
id: "header-jobs",
|
||||
icon: <Icon component={FaCarCrash} />,
|
||||
icon: <FaCarCrash />,
|
||||
label: t("menus.header.jobs"),
|
||||
children: [
|
||||
{
|
||||
@@ -456,20 +369,14 @@ function Header({
|
||||
icon: <FileAddOutlined />,
|
||||
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
id: "header-jobs-divider"
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "alljobs",
|
||||
id: "header-all-jobs",
|
||||
icon: <UnorderedListOutlined />,
|
||||
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
id: "header-jobs-divider2"
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "productionlist",
|
||||
id: "header-production-list",
|
||||
@@ -479,7 +386,7 @@ function Header({
|
||||
{
|
||||
key: "productionboard",
|
||||
id: "header-production-board",
|
||||
icon: <Icon component={BsKanban} />,
|
||||
icon: <BsKanban />,
|
||||
label: (
|
||||
<Link to="/manage/production/board">
|
||||
<LockWrapper featureName="visualboard" bodyshop={bodyshop}>
|
||||
@@ -488,10 +395,7 @@ function Header({
|
||||
</Link>
|
||||
)
|
||||
},
|
||||
{
|
||||
type: "divider",
|
||||
id: "header-jobs-divider3"
|
||||
},
|
||||
{ type: "divider" },
|
||||
{
|
||||
key: "scoreboard",
|
||||
id: "header-scoreboard",
|
||||
@@ -508,8 +412,8 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "customers",
|
||||
icon: <UserOutlined />,
|
||||
id: "header-customers",
|
||||
icon: <UserOutlined />,
|
||||
label: t("menus.header.customers"),
|
||||
children: [
|
||||
{
|
||||
@@ -614,12 +518,7 @@ function Header({
|
||||
id: "header-create-task",
|
||||
icon: <PlusCircleOutlined />,
|
||||
label: t("menus.header.create_task"),
|
||||
onClick: () => {
|
||||
setTaskUpsertContext({
|
||||
actions: {},
|
||||
context: {}
|
||||
});
|
||||
}
|
||||
onClick: () => setTaskUpsertContext({ actions: {}, context: {} })
|
||||
},
|
||||
{
|
||||
key: "mytasks",
|
||||
@@ -644,7 +543,7 @@ function Header({
|
||||
{
|
||||
key: "shop",
|
||||
id: "header-shop",
|
||||
icon: <Icon component={GiSettingsKnobs} />,
|
||||
icon: <GiSettingsKnobs />,
|
||||
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
|
||||
},
|
||||
{
|
||||
@@ -662,23 +561,18 @@ function Header({
|
||||
id: "header-reportcenter",
|
||||
icon: <BarChartOutlined />,
|
||||
label: t("menus.header.reportcenter"),
|
||||
onClick: () => {
|
||||
setReportCenterContext({
|
||||
actions: {},
|
||||
context: {}
|
||||
});
|
||||
}
|
||||
onClick: () => setReportCenterContext({ actions: {}, context: {} })
|
||||
},
|
||||
{
|
||||
key: "shop-vendors",
|
||||
id: "header-shop-vendors",
|
||||
icon: <Icon component={IoBusinessOutline} />,
|
||||
icon: <IoBusinessOutline />,
|
||||
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
|
||||
},
|
||||
{
|
||||
key: "shop-csi",
|
||||
id: "header-shop-csi",
|
||||
icon: <Icon component={RiSurveyLine} />,
|
||||
icon: <RiSurveyLine />,
|
||||
label: (
|
||||
<Link to="/manage/shop/csi">
|
||||
<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",
|
||||
icon: <ClockCircleFilled />,
|
||||
id: "header-recent",
|
||||
icon: <ClockCircleFilled />,
|
||||
children: recentItems.map((i, idx) => ({
|
||||
key: idx,
|
||||
id: `header-recent-${idx}`,
|
||||
@@ -714,13 +595,13 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "user",
|
||||
id: "header-user",
|
||||
icon: <UserOutlined />,
|
||||
// label: currentUser.displayName || currentUser.email || t("general.labels.unknown"),
|
||||
children: [
|
||||
{
|
||||
key: "signout",
|
||||
id: "header-signout",
|
||||
icon: <Icon component={FiLogOut} />,
|
||||
icon: <FiLogOut />,
|
||||
danger: true,
|
||||
label: t("user.actions.signout"),
|
||||
onClick: () => signOutStart()
|
||||
@@ -728,32 +609,25 @@ function Header({
|
||||
{
|
||||
key: "help",
|
||||
id: "header-help",
|
||||
icon: <Icon component={QuestionCircleFilled} />,
|
||||
icon: <QuestionCircleFilled />,
|
||||
label: t("menus.header.help"),
|
||||
onClick: () => {
|
||||
window.open("https://help.imex.online/", "_blank");
|
||||
}
|
||||
onClick: () => window.open("https://help.imex.online/", "_blank")
|
||||
},
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: false
|
||||
})
|
||||
...(InstanceRenderManager({ imex: true, rome: false })
|
||||
? [
|
||||
{
|
||||
key: "rescue",
|
||||
id: "header-rescue",
|
||||
icon: <Icon component={CarFilled} />,
|
||||
icon: <CarFilled />,
|
||||
label: t("menus.header.rescueme"),
|
||||
onClick: () => {
|
||||
window.open("https://imexrescue.com/", "_blank");
|
||||
}
|
||||
onClick: () => window.open("https://imexrescue.com/", "_blank")
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "shiftclock",
|
||||
id: "header-shiftclock",
|
||||
icon: <Icon component={GiPlayerTime} />,
|
||||
icon: <GiPlayerTime />,
|
||||
label: (
|
||||
<Link to="/manage/shiftclock">
|
||||
<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 (
|
||||
<Layout.Header style={{ padding: 0 }}>
|
||||
{isMobile ? (
|
||||
<Layout.Header style={{ padding: 0, background: "#001529" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
height: "100%",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
theme="dark"
|
||||
selectedKeys={[selectedHeader]}
|
||||
onClick={handleMenuClick}
|
||||
subMenuCloseDelay={0.3}
|
||||
items={menuItems}
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
items={leftMenuItems}
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%"
|
||||
flex: "1 1 auto",
|
||||
minWidth: 0,
|
||||
overflowX: "auto",
|
||||
borderBottom: "none",
|
||||
background: "transparent"
|
||||
}}
|
||||
>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
theme="dark"
|
||||
selectedKeys={[selectedHeader]}
|
||||
onClick={handleMenuClick}
|
||||
subMenuCloseDelay={0.3}
|
||||
items={menuItems.slice(0, -3)}
|
||||
style={{
|
||||
flex: "0 1 auto",
|
||||
justifyContent: "flex-start",
|
||||
minWidth: 0,
|
||||
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>
|
||||
)}
|
||||
/>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
theme="dark"
|
||||
selectedKeys={[selectedHeader]}
|
||||
onClick={handleMenuClick}
|
||||
subMenuCloseDelay={0.3}
|
||||
items={notificationItem}
|
||||
style={{ flex: "0 0 auto", minWidth: 0, borderBottom: "none", background: "transparent" }}
|
||||
/>
|
||||
</div>
|
||||
<NotificationCenterContainer visible={notificationVisible} onClose={() => setNotificationVisible(false)} />
|
||||
</Layout.Header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const { dispatchEmailsToQueue } = require("./queues/emailQueue");
|
||||
const { dispatchAppsToQueue } = require("./queues/appQueue");
|
||||
|
||||
// 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.
|
||||
|
||||
Reference in New Issue
Block a user