@@ -1,6 +1,6 @@
|
||||
import { Badge, Layout, Menu, Spin } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -95,7 +95,8 @@ function Header({
|
||||
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 {
|
||||
@@ -123,6 +124,40 @@ function Header({
|
||||
}
|
||||
}, [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);
|
||||
@@ -656,7 +691,7 @@ function Header({
|
||||
icon: unreadLoading ? (
|
||||
<Spin size="small" />
|
||||
) : (
|
||||
<Badge size="small" count={unreadCount}>
|
||||
<Badge offset={[8, 0]} size="small" count={unreadCount}>
|
||||
<BellFilled />
|
||||
</Badge>
|
||||
),
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Virtuoso } from "react-virtuoso";
|
||||
import { Alert, Badge, Button, Space, Spin, Switch, Tooltip, Typography } from "antd";
|
||||
import { Badge, Button, Space, Spin, Switch, Tooltip, Typography } from "antd";
|
||||
import { CheckCircleFilled, CheckCircleOutlined, EyeFilled, EyeOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import "./notification-center.styles.scss";
|
||||
import day from "../../utils/day.js";
|
||||
import { forwardRef } from "react";
|
||||
import { DateTimeFormat } from "../../utils/DateFormatter.jsx";
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
|
||||
@@ -52,11 +53,7 @@ const NotificationCenterComponent = forwardRef(
|
||||
<span className="ro-number">
|
||||
{t("notifications.labels.ro-number", { ro_number: notification.roNumber })}
|
||||
</span>
|
||||
<Text
|
||||
type="secondary"
|
||||
className="relative-time"
|
||||
title={day(notification.created_at).format("YYYY-MM-DD hh:mm A")}
|
||||
>
|
||||
<Text type="secondary" className="relative-time" title={DateTimeFormat(notification.created_at)}>
|
||||
{day(notification.created_at).fromNow()}
|
||||
</Text>
|
||||
</Title>
|
||||
@@ -83,7 +80,6 @@ const NotificationCenterComponent = forwardRef(
|
||||
<div className="notification-controls">
|
||||
<Tooltip title={t("notifications.labels.show-unread-only")}>
|
||||
<Space size={4} align="center" className="notification-toggle">
|
||||
{" "}
|
||||
{showUnreadOnly ? (
|
||||
<EyeFilled className="notification-toggle-icon" />
|
||||
) : (
|
||||
@@ -94,7 +90,7 @@ const NotificationCenterComponent = forwardRef(
|
||||
</Tooltip>
|
||||
<Tooltip title={t("notifications.labels.mark-all-read")}>
|
||||
<Button
|
||||
type={!unreadCount ? "default" : "primary"}
|
||||
type="link"
|
||||
icon={!unreadCount ? <CheckCircleFilled /> : <CheckCircleOutlined />}
|
||||
onClick={markAllRead}
|
||||
disabled={!unreadCount}
|
||||
|
||||
@@ -9,9 +9,10 @@ import {
|
||||
GET_NOTIFICATIONS,
|
||||
GET_UNREAD_COUNT,
|
||||
MARK_ALL_NOTIFICATIONS_READ,
|
||||
MARK_NOTIFICATION_READ
|
||||
MARK_NOTIFICATION_READ,
|
||||
UPDATE_NOTIFICATIONS_READ_FRAGMENT
|
||||
} from "../../graphql/notifications.queries.js";
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const SocketContext = createContext(null);
|
||||
@@ -290,7 +291,17 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
|
||||
});
|
||||
|
||||
notification.info({
|
||||
message: t("notifications.labels.notification-popup-title", { ro_number: jobRoNumber }),
|
||||
message: (
|
||||
<div
|
||||
onClick={() => {
|
||||
markNotificationRead({ variables: { id: notificationId } })
|
||||
.then(() => navigate(`/manage/jobs/${jobId}`))
|
||||
.catch((e) => console.error(`Error marking notification read: ${e?.message || ""}`));
|
||||
}}
|
||||
>
|
||||
{t("notifications.labels.notification-popup-title", { ro_number: jobRoNumber })}
|
||||
</div>
|
||||
),
|
||||
description: (
|
||||
<ul
|
||||
className="notification-alert-unordered-list"
|
||||
@@ -327,11 +338,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
|
||||
});
|
||||
client.cache.writeFragment({
|
||||
id: notificationRef,
|
||||
fragment: gql`
|
||||
fragment UpdateNotificationRead on notifications {
|
||||
read
|
||||
}
|
||||
`,
|
||||
fragment: UPDATE_NOTIFICATIONS_READ_FRAGMENT,
|
||||
data: { read: timestamp }
|
||||
});
|
||||
|
||||
@@ -383,11 +390,7 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser, scenarioNot
|
||||
const notifRef = client.cache.identify({ __typename: "notifications", id: notif.id });
|
||||
client.cache.writeFragment({
|
||||
id: notifRef,
|
||||
fragment: gql`
|
||||
fragment UpdateNotificationRead on notifications {
|
||||
read
|
||||
}
|
||||
`,
|
||||
fragment: UPDATE_NOTIFICATIONS_READ_FRAGMENT,
|
||||
data: { read: timestamp }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -50,3 +50,9 @@ export const MARK_NOTIFICATION_READ = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_NOTIFICATIONS_READ_FRAGMENT = gql`
|
||||
fragment UpdateNotificationRead on notifications {
|
||||
read
|
||||
}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user