import { BankFilled, BarChartOutlined, BellFilled, CarFilled, CheckCircleOutlined, ClockCircleFilled, DashboardFilled, DollarCircleFilled, ExportOutlined, FieldTimeOutlined, FileAddFilled, FileAddOutlined, FileFilled, HomeFilled, ImportOutlined, LineChartOutlined, PaperClipOutlined, PhoneOutlined, PlusCircleOutlined, QuestionCircleFilled, ScheduleOutlined, SettingOutlined, TeamOutlined, ToolFilled, UnorderedListOutlined, UserOutlined } from "@ant-design/icons"; import { useQuery } from "@apollo/client"; import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { Badge, Layout, Menu, Spin } from "antd"; import { useEffect, useRef, useState } from "react"; 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 day from "../../utils/day.js"; import InstanceRenderManager from "../../utils/instanceRenderMgr"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import LockWrapper from "../lock-wrapper/lock-wrapper.component"; import NotificationCenterContainer from "../notification-center/notification-center.container.jsx"; import { useSocket } from "../../contexts/SocketIO/useSocket.js"; // Redux mappings const mapStateToProps = createStructuredSelector({ currentUser: selectCurrentUser, recentItems: selectRecentItems, selectedHeader: selectSelectedHeader, bodyshop: selectBodyshop }); const mapDispatchToProps = (dispatch) => ({ 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, modal: "cardPayment" })), setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" })) }); function Header({ handleMenuClick, currentUser, bodyshop, selectedHeader, signOutStart, setBillEnterContext, setTimeTicketContext, setPaymentContext, setReportCenterContext, recentItems, setCardPaymentContext, setTaskUpsertContext }) { const { treatments: { ImEXPay, DmsAp, Simple_Inventory } } = useSplitTreatments({ attributes: {}, names: ["ImEXPay", "DmsAp", "Simple_Inventory"], splitKey: bodyshop && bodyshop.imexshopid }); const { t } = useTranslation(); const { isConnected, scenarioNotificationsOn } = useSocket(); const [notificationVisible, setNotificationVisible] = useState(false); const baseTitleRef = useRef(document.title || ""); const lastSetTitleRef = useRef(""); const userAssociationId = bodyshop?.associations?.[0]?.id; const { data: unreadData, refetch: refetchUnread, loading: unreadLoading } = useQuery(GET_UNREAD_COUNT, { variables: { associationid: userAssociationId }, fetchPolicy: "network-only", pollInterval: isConnected ? 0 : day.duration(60, "seconds").asMilliseconds(), skip: !userAssociationId || !scenarioNotificationsOn }); const unreadCount = unreadData?.notifications_aggregate?.aggregate?.count ?? 0; useEffect(() => { if (userAssociationId) { refetchUnread().catch((e) => console.error(`Error fetching unread notifications: ${e?.message}`)); } }, [refetchUnread, userAssociationId]); useEffect(() => { if (!isConnected && !unreadLoading && userAssociationId) { refetchUnread().catch((e) => console.error(`Error fetching unread notifications: ${e?.message}`)); } }, [isConnected, unreadLoading, refetchUnread, userAssociationId]); // Keep The unread count in the title. useEffect(() => { const updateTitle = () => { const currentTitle = document.title; // Check if the current title differs from what we last set if (currentTitle !== lastSetTitleRef.current) { // Extract base title by removing any unread count prefix const baseTitleMatch = currentTitle.match(/^\(\d+\)\s*(.*)$/); baseTitleRef.current = baseTitleMatch ? baseTitleMatch[1] : currentTitle; } // Apply unread count to the base title const newTitle = unreadCount > 0 ? `(${unreadCount}) ${baseTitleRef.current}` : baseTitleRef.current; // Only update if the title has changed to avoid unnecessary DOM writes if (document.title !== newTitle) { document.title = newTitle; lastSetTitleRef.current = newTitle; // Store what we set } }; // Initial update updateTitle(); // Poll every 100ms to catch child component changes const interval = setInterval(updateTitle, 100); // Cleanup return () => { clearInterval(interval); document.title = baseTitleRef.current; // Reset to base title on unmount }; }, [unreadCount]); // Re-run when unreadCount changes const handleNotificationClick = (e) => { setNotificationVisible(!notificationVisible); if (handleMenuClick) handleMenuClick(e); }; const accountingChildren = [ { key: "bills", id: "header-accounting-bills", icon: , label: ( {t("menus.header.bills")} ) }, { key: "enterbills", id: "header-accounting-enterbills", icon: , label: ( {t("menus.header.enterbills")} ), onClick: () => HasFeatureAccess({ featureName: "bills", bodyshop }) && setBillEnterContext({ actions: {}, context: {} }) }, ...(Simple_Inventory.treatment === "on" ? [ { type: "divider" }, { key: "inventory", id: "header-accounting-inventory", icon: , label: {t("menus.header.inventory")} } ] : []), { type: "divider" }, { key: "allpayments", id: "header-accounting-allpayments", icon: , label: {t("menus.header.allpayments")} }, { key: "enterpayments", id: "header-accounting-enterpayments", icon: , label: t("menus.header.enterpayment"), onClick: () => setPaymentContext({ actions: {}, context: null }) }, ...(ImEXPay.treatment === "on" ? [ { key: "entercardpayments", id: "header-accounting-entercardpayments", icon: , label: t("menus.header.entercardpayment"), onClick: () => setCardPaymentContext({ actions: {}, context: {} }) } ] : []), { type: "divider" }, { key: "timetickets", id: "header-accounting-timetickets", icon: , label: ( {t("menus.header.timetickets")} ) }, ...(bodyshop?.md_tasks_presets?.use_approvals ? [ { key: "ttapprovals", id: "header-accounting-ttapprovals", icon: , label: {t("menus.header.ttapprovals")} } ] : []), { key: "entertimetickets", id: "header-accounting-entertimetickets", icon: , label: ( {t("menus.header.entertimeticket")} ), onClick: () => HasFeatureAccess({ featureName: "timetickets", bodyshop }) && setTimeTicketContext({ actions: {}, context: { created_by: currentUser.displayName ? `${currentUser.email} | ${currentUser.displayName}` : currentUser.email } }) }, { type: "divider" }, { key: "accountingexport", id: "header-accounting-export", icon: , label: ( {t("menus.header.export")} ), children: [ { key: "receivables", id: "header-accounting-receivables", label: ( {t("menus.header.accounting-receivables")} ) }, ...(!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || DmsAp.treatment === "on" ? [ { key: "payables", id: "header-accounting-payables", label: ( {t("menus.header.accounting-payables")} ) } ] : []), ...(!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) ? [ { key: "payments", id: "header-accounting-payments", label: ( {t("menus.header.accounting-payments")} ) } ] : []), { type: "divider" }, { key: "exportlogs", id: "header-accounting-exportlogs", label: ( {t("menus.header.export-logs")} ) } ] } ]; // Left menu items (includes original navigation items) const leftMenuItems = [ { key: "home", id: "header-home", icon: , label: {t("menus.header.home")} }, { key: "schedule", id: "header-schedule", icon: , label: {t("menus.header.schedule")} }, { key: "jobssubmenu", id: "header-jobs", icon: , label: t("menus.header.jobs"), children: [ { key: "activejobs", id: "header-active-jobs", icon: , label: {t("menus.header.activejobs")} }, { key: "readyjobs", id: "header-ready-jobs", icon: , label: {t("menus.header.readyjobs")} }, { key: "parts-queue", id: "header-parts-queue", icon: , label: {t("menus.header.parts-queue")} }, { key: "availablejobs", id: "header-jobs-available", icon: , label: {t("menus.header.availablejobs")} }, { key: "newjob", id: "header-new-job", icon: , label: {t("menus.header.newjob")} }, { type: "divider" }, { key: "alljobs", id: "header-all-jobs", icon: , label: {t("menus.header.alljobs")} }, { type: "divider" }, { key: "productionlist", id: "header-production-list", icon: , label: {t("menus.header.productionlist")} }, { key: "productionboard", id: "header-production-board", icon: , label: ( {t("menus.header.productionboard")} ) }, { type: "divider" }, { key: "scoreboard", id: "header-scoreboard", icon: , label: ( {t("menus.header.scoreboard")} ) } ] }, { key: "customers", id: "header-customers", icon: , label: t("menus.header.customers"), children: [ { key: "owners", id: "header-owners", icon: , label: {t("menus.header.owners")} }, { key: "vehicles", id: "header-vehicles", icon: , label: {t("menus.header.vehicles")} } ] }, { key: "ccs", id: "header-css", icon: , label: ( {t("menus.header.courtesycars")} ), children: [ { key: "courtesycarsall", id: "header-courtesycars-all", icon: , label: ( {t("menus.header.courtesycars-all")} ) }, { key: "contracts", id: "header-contracts", icon: , label: ( {t("menus.header.courtesycars-contracts")} ) }, { key: "newcontract", id: "header-newcontract", icon: , label: ( {t("menus.header.courtesycars-newcontract")} ) } ] }, ...(accountingChildren.length > 0 ? [ { key: "accounting", id: "header-accounting", icon: , label: t("menus.header.accounting"), children: accountingChildren } ] : []), { key: "phonebook", id: "header-phonebook", icon: , label: {t("menus.header.phonebook")} }, { key: "temporarydocs", id: "header-temporarydocs", icon: , label: ( {t("menus.header.temporarydocs")} ) }, { key: "tasks", id: "tasks", icon: , label: t("menus.header.tasks"), children: [ { key: "createTask", id: "header-create-task", icon: , label: t("menus.header.create_task"), onClick: () => setTaskUpsertContext({ actions: {}, context: {} }) }, { key: "mytasks", id: "header-my-tasks", icon: , label: {t("menus.header.my_tasks")} }, { key: "all_tasks", id: "header-all-tasks", icon: , label: {t("menus.header.all_tasks")} } ] }, { key: "shopsubmenu", id: "header-shopsubmenu", icon: , label: t("menus.header.shop"), children: [ { key: "shop", id: "header-shop", icon: , label: {t("menus.header.shop_config")} }, { key: "dashboard", id: "header-dashboard", icon: , label: ( {t("menus.header.dashboard")} ) }, { key: "reportcenter", id: "header-reportcenter", icon: , label: t("menus.header.reportcenter"), onClick: () => setReportCenterContext({ actions: {}, context: {} }) }, { key: "shop-vendors", id: "header-shop-vendors", icon: , label: {t("menus.header.shop_vendors")} }, { key: "shop-csi", id: "header-shop-csi", icon: , label: ( {t("menus.header.shop_csi")} ) } ] }, { key: "recent", id: "header-recent", icon: , label: t("menus.header.recent"), children: recentItems.map((i, idx) => ({ key: idx, id: `header-recent-${idx}`, label: {i.label} })) }, { key: "user", id: "header-user", icon: , label: t("menus.currentuser.profile"), children: [ { key: "signout", id: "header-signout", icon: , danger: true, label: t("user.actions.signout"), onClick: () => signOutStart() }, { key: "help", id: "header-help", icon: , label: t("menus.header.help"), onClick: () => window.open("https://help.imex.online/", "_blank") }, ...(InstanceRenderManager({ imex: true, rome: false }) ? [ { key: "rescue", id: "header-rescue", icon: , label: t("menus.header.rescueme"), onClick: () => window.open("https://imexrescue.com/", "_blank") } ] : []), { key: "shiftclock", id: "header-shiftclock", icon: , label: ( {t("menus.header.shiftclock")} ) }, { key: "profile", id: "header-profile", icon: , label: {t("menus.currentuser.profile")} } ] } ]; // Notifications item (always on the right) const notificationItem = scenarioNotificationsOn ? [ { key: "notifications", id: "header-notifications", icon: unreadLoading ? ( ) : ( ), onClick: handleNotificationClick } ] : []; return (
{scenarioNotificationsOn && ( )}
{scenarioNotificationsOn && ( setNotificationVisible(false)} unreadCount={unreadCount} /> )}
); } export default connect(mapStateToProps, mapDispatchToProps)(Header);