Merged in release/2024-03-14 (pull request #2155)

Release/2024 03 14
This commit is contained in:
Dave Richer
2025-03-05 22:33:06 +00:00
9 changed files with 366 additions and 24 deletions

View File

@@ -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>
),

View File

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

View File

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

View File

@@ -50,3 +50,9 @@ export const MARK_NOTIFICATION_READ = gql`
}
}
`;
export const UPDATE_NOTIFICATIONS_READ_FRAGMENT = gql`
fragment UpdateNotificationRead on notifications {
read
}
`;