diff --git a/client/src/components/notification-center/notification-center.container.jsx b/client/src/components/notification-center/notification-center.container.jsx index 895ba03f5..90b9c9809 100644 --- a/client/src/components/notification-center/notification-center.container.jsx +++ b/client/src/components/notification-center/notification-center.container.jsx @@ -1,13 +1,8 @@ import { useCallback, useEffect, useMemo, useState } from "react"; -import { useMutation, useQuery } from "@apollo/client"; +import { useQuery } from "@apollo/client"; import { connect } from "react-redux"; import NotificationCenterComponent from "./notification-center.component"; -import { - GET_NOTIFICATIONS, - MARK_ALL_NOTIFICATIONS_READ, - MARK_NOTIFICATION_READ, - GET_UNREAD_COUNT -} from "../../graphql/notifications.queries"; +import { GET_NOTIFICATIONS } from "../../graphql/notifications.queries"; import { useSocket } from "../../contexts/SocketIO/socketContext.jsx"; import { createStructuredSelector } from "reselect"; import { selectBodyshop } from "../../redux/user/user.selectors.js"; @@ -16,7 +11,7 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) { const [showUnreadOnly, setShowUnreadOnly] = useState(false); const [notifications, setNotifications] = useState([]); const [error, setError] = useState(null); - const { isConnected } = useSocket(); + const { isConnected, markNotificationRead, markAllNotificationsRead } = useSocket(); const userAssociationId = bodyshop?.associations?.[0]?.id; @@ -51,106 +46,6 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) { } }); - const [markAllReadMutation, { error: mutationError }] = useMutation(MARK_ALL_NOTIFICATIONS_READ, { - variables: { associationid: userAssociationId }, - update: (cache, { data: mutationData }) => { - const timestamp = new Date().toISOString(); - cache.modify({ - fields: { - notifications(existing = [], { readField }) { - return existing.map((notif) => { - if (readField("read", notif) === null && readField("associationid", notif) === userAssociationId) { - return { ...notif, read: timestamp }; - } - return notif; - }); - }, - notifications_aggregate() { - return { aggregate: { count: 0, __typename: "notifications_aggregate_fields" } }; - } - } - }); - - if (isConnected) { - const cachedNotifications = cache.readQuery({ - query: GET_NOTIFICATIONS, - variables: { - limit: 20, - offset: 0, - where: whereClause - } - }); - - if (cachedNotifications?.notifications) { - cache.writeQuery({ - query: GET_NOTIFICATIONS, - variables: { - limit: 20, - offset: 0, - where: whereClause - }, - data: { - notifications: cachedNotifications.notifications.map((notif) => - notif.read === null ? { ...notif, read: timestamp } : notif - ) - } - }); - } - } - }, - onError: (err) => { - setError(err.message); - console.error("MARK_ALL_NOTIFICATIONS_READ error:", err); - } - }); - - const [markNotificationRead] = useMutation(MARK_NOTIFICATION_READ, { - update: (cache, { data: { update_notifications } }) => { - const timestamp = new Date().toISOString(); - const updatedNotification = update_notifications.returning[0]; - - // Update the notifications list - cache.modify({ - fields: { - notifications(existing = [], { readField }) { - return existing.map((notif) => { - if (readField("id", notif) === updatedNotification.id) { - return { ...notif, read: timestamp }; - } - return notif; - }); - } - } - }); - - // Update the unread count in notifications_aggregate - const unreadCountQuery = cache.readQuery({ - query: GET_UNREAD_COUNT, - variables: { associationid: userAssociationId } - }); - - if (unreadCountQuery?.notifications_aggregate?.aggregate?.count > 0) { - cache.writeQuery({ - query: GET_UNREAD_COUNT, - variables: { associationid: userAssociationId }, - data: { - notifications_aggregate: { - ...unreadCountQuery.notifications_aggregate, - aggregate: { - ...unreadCountQuery.notifications_aggregate.aggregate, - count: unreadCountQuery.notifications_aggregate.aggregate.count - 1 - } - } - } - }); - } - }, - onError: (err) => { - setError(err.message); - console.error("MARK_NOTIFICATION_READ error:", err); - } - }); - useEffect(() => { if (data?.notifications) { const processedNotifications = data.notifications @@ -187,10 +82,10 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) { }, [data]); useEffect(() => { - if (queryError || mutationError) { - setError(queryError?.message || mutationError?.message); + if (queryError) { + setError(queryError.message); } - }, [queryError, mutationError]); + }, [queryError]); const loadMore = useCallback(() => { if (!loading && data?.notifications.length) { @@ -213,19 +108,24 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) { setShowUnreadOnly(value); }; - const handleMarkAllRead = () => { - markAllReadMutation() + const handleMarkAllRead = useCallback(() => { + markAllNotificationsRead() .then(() => { const timestamp = new Date().toISOString(); setNotifications((prev) => { const updatedNotifications = prev.map((notif) => - notif.read === null && notif.associationid === userAssociationId ? { ...notif, read: timestamp } : notif + notif.read === null && notif.associationid === userAssociationId + ? { + ...notif, + read: timestamp + } + : notif ); return [...updatedNotifications]; }); }) .catch((e) => console.error(`Error marking all notifications read: ${e?.message || ""}`)); - }; + }, [markAllNotificationsRead, userAssociationId]); const handleNotificationClick = useCallback( (notificationId) => { diff --git a/client/src/contexts/SocketIO/socketContext.jsx b/client/src/contexts/SocketIO/socketContext.jsx index d82557475..e598d0274 100644 --- a/client/src/contexts/SocketIO/socketContext.jsx +++ b/client/src/contexts/SocketIO/socketContext.jsx @@ -5,7 +5,13 @@ import { store } from "../../redux/store"; import { addAlerts, setWssStatus } from "../../redux/application/application.actions"; import client from "../../utils/GraphQLClient"; import { useNotification } from "../Notifications/notificationContext.jsx"; -import { GET_NOTIFICATIONS } from "../../graphql/notifications.queries.js"; +import { + GET_NOTIFICATIONS, + GET_UNREAD_COUNT, + MARK_NOTIFICATION_READ, + MARK_ALL_NOTIFICATIONS_READ +} from "../../graphql/notifications.queries.js"; +import { useMutation } from "@apollo/client"; const SocketContext = createContext(null); @@ -16,6 +22,103 @@ export const SocketProvider = ({ children, bodyshop }) => { const notification = useNotification(); const userAssociationId = bodyshop?.associations?.[0]?.id; + const [markNotificationRead] = useMutation(MARK_NOTIFICATION_READ, { + update: (cache, { data: { update_notifications } }) => { + const timestamp = new Date().toISOString(); + const updatedNotification = update_notifications.returning[0]; + + // Update the notifications list + cache.modify({ + fields: { + notifications(existing = [], { readField }) { + return existing.map((notif) => { + if (readField("id", notif) === updatedNotification.id) { + return { ...notif, read: timestamp }; + } + return notif; + }); + } + } + }); + + // Update the unread count in notifications_aggregate + const unreadCountQuery = cache.readQuery({ + query: GET_UNREAD_COUNT, + variables: { associationid: userAssociationId } + }); + + if (unreadCountQuery?.notifications_aggregate?.aggregate?.count > 0) { + cache.writeQuery({ + query: GET_UNREAD_COUNT, + variables: { associationid: userAssociationId }, + data: { + notifications_aggregate: { + ...unreadCountQuery.notifications_aggregate, + aggregate: { + ...unreadCountQuery.notifications_aggregate.aggregate, + count: unreadCountQuery.notifications_aggregate.aggregate.count - 1 + } + } + } + }); + } + }, + onError: (err) => { + console.error("MARK_NOTIFICATION_READ error in SocketProvider:", err); + } + }); + + const [markAllNotificationsRead] = useMutation(MARK_ALL_NOTIFICATIONS_READ, { + variables: { associationid: userAssociationId }, + update: (cache) => { + const timestamp = new Date().toISOString(); + cache.modify({ + fields: { + notifications(existing = [], { readField }) { + return existing.map((notif) => { + if (readField("read", notif) === null && readField("associationid", notif) === userAssociationId) { + return { ...notif, read: timestamp }; + } + return notif; + }); + }, + notifications_aggregate() { + return { aggregate: { count: 0, __typename: "notifications_aggregate_fields" } }; + } + } + }); + + const baseWhereClause = { associationid: { _eq: userAssociationId } }; + const cachedNotifications = cache.readQuery({ + query: GET_NOTIFICATIONS, + variables: { + limit: 20, + offset: 0, + where: baseWhereClause + } + }); + + if (cachedNotifications?.notifications) { + cache.writeQuery({ + query: GET_NOTIFICATIONS, + variables: { + limit: 20, + offset: 0, + where: baseWhereClause + }, + data: { + notifications: cachedNotifications.notifications.map((notif) => + notif.read === null ? { ...notif, read: timestamp } : notif + ) + } + }); + } + }, + onError: (err) => { + console.error("MARK_ALL_NOTIFICATIONS_READ error in SocketProvider:", err); + } + }); + useEffect(() => { const initializeSocket = async (token) => { if (!bodyshop || !bodyshop.id || socketRef.current) return; @@ -86,8 +189,6 @@ export const SocketProvider = ({ children, bodyshop }) => { const handleNotification = (data) => { const { jobId, jobRoNumber, notificationId, associationId, notifications } = data; - // Filter out notifications not matching the user's associationId - // Technically not required. if (associationId !== userAssociationId) return; const newNotification = { @@ -176,7 +277,14 @@ export const SocketProvider = ({ children, bodyshop }) => { notification.info({ message: "New Notification", description: ( -