feature/IO-3096-GlobalNotifications - Checkpoint - Fix user getting all bodyshop notifications (now by associationId), fix regression in 'Assigned To' scenario.

This commit is contained in:
Dave Richer
2025-02-26 13:11:49 -05:00
parent b86309e74b
commit 0767e290f4
8 changed files with 99 additions and 65 deletions

View File

@@ -49,7 +49,7 @@ 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 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
@@ -129,27 +129,33 @@ function Header({
const { isConnected } = useSocket(bodyshop);
const [notificationVisible, setNotificationVisible] = useState(false);
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 : 30000 // Poll only if socket is down
pollInterval: isConnected ? 0 : 30000, // Poll only if socket is down
skip: !userAssociationId // Skip query if no userAssociationId
});
const unreadCount = unreadData?.notifications_aggregate?.aggregate?.count ?? 0;
// Initial fetch and socket status handling
useEffect(() => {
refetchUnread();
}, [refetchUnread]);
if (userAssociationId) {
refetchUnread().catch((e) => console.error(`Something went wrong fetching unread notifications: ${e?.message}`));
}
}, [refetchUnread, userAssociationId]);
useEffect(() => {
if (!isConnected && !unreadLoading) {
refetchUnread();
if (!isConnected && !unreadLoading && userAssociationId) {
refetchUnread().catch((e) => console.error(`Something went wrong fetching unread notifications: ${e?.message}`));
}
}, [isConnected, unreadLoading, refetchUnread]);
}, [isConnected, unreadLoading, refetchUnread, userAssociationId]);
const handleNotificationClick = (e) => {
setNotificationVisible(!notificationVisible);

View File

@@ -1,10 +1,11 @@
// notification-center.container.jsx
import { useState, useEffect, useCallback } from "react";
import { useQuery, useMutation } from "@apollo/client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useMutation, useQuery } from "@apollo/client";
import { connect } from "react-redux";
import NotificationCenterComponent from "./notification-center.component";
import { GET_NOTIFICATIONS, MARK_ALL_NOTIFICATIONS_READ } from "../../graphql/notifications.queries";
import { useSocket } from "../../contexts/SocketIO/socketContext.jsx";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors.js";
export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
const [showUnreadOnly, setShowUnreadOnly] = useState(false);
@@ -12,6 +13,16 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
const [error, setError] = useState(null);
const { isConnected } = useSocket();
const userAssociationId = bodyshop?.associations?.[0]?.id;
const baseWhereClause = useMemo(() => {
return { associationid: { _eq: userAssociationId } };
}, [userAssociationId]);
const whereClause = useMemo(() => {
return showUnreadOnly ? { ...baseWhereClause, read: { _is_null: true } } : baseWhereClause;
}, [baseWhereClause, showUnreadOnly]);
const {
data,
fetchMore,
@@ -22,12 +33,12 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
variables: {
limit: 20,
offset: 0,
where: showUnreadOnly ? { read: { _is_null: true } } : {}
where: whereClause
},
fetchPolicy: "cache-and-network",
notifyOnNetworkStatusChange: true,
pollInterval: isConnected ? 0 : 30000,
skip: false,
skip: !userAssociationId, // Skip query if no userAssociationId
onError: (err) => {
setError(err.message);
console.error("GET_NOTIFICATIONS error:", err);
@@ -36,13 +47,14 @@ 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) {
if (readField("read", notif) === null && readField("associationid", notif) === userAssociationId) {
return { ...notif, read: timestamp };
}
return notif;
@@ -60,7 +72,7 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
variables: {
limit: 20,
offset: 0,
where: showUnreadOnly ? { read: { _is_null: true } } : {}
where: whereClause
}
});
@@ -70,7 +82,7 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
variables: {
limit: 20,
offset: 0,
where: showUnreadOnly ? { read: { _is_null: true } } : {}
where: whereClause
},
data: {
notifications: cachedNotifications.notifications.map((notif) =>
@@ -102,21 +114,19 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
scenarioMeta = {};
}
if (!Array.isArray(scenarioText)) scenarioText = [scenarioText];
// Derive RO number from scenario_meta or assume it's available in notif
const roNumber = notif.job.ro_number || "RO Not Found"; // Adjust based on your data structure
const roNumber = notif.job.ro_number;
if (!Array.isArray(scenarioMeta)) scenarioMeta = [scenarioMeta];
const processed = {
return {
id: notif.id,
jobid: notif.jobid,
associationid: notif.associationid,
scenarioText,
scenarioMeta,
roNumber, // Add RO number to notification object
roNumber,
created_at: notif.created_at,
read: notif.read,
__typename: notif.__typename
};
return processed;
})
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
setNotifications(processedNotifications);
@@ -133,7 +143,7 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
const loadMore = useCallback(() => {
if (!loading && data?.notifications.length) {
fetchMore({
variables: { offset: data.notifications.length },
variables: { offset: data.notifications.length, where: whereClause },
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return {
@@ -145,7 +155,7 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
console.error("Fetch more error:", err);
});
}
}, [data?.notifications?.length, fetchMore, loading]);
}, [data?.notifications?.length, fetchMore, loading, whereClause]);
const handleToggleUnreadOnly = (value) => {
setShowUnreadOnly(value);
@@ -157,7 +167,12 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
const timestamp = new Date().toISOString();
setNotifications((prev) => {
const updatedNotifications = prev.map((notif) =>
notif.read === null ? { ...notif, read: timestamp } : notif
notif.read === null && notif.associationid === userAssociationId
? {
...notif,
read: timestamp
}
: notif
);
return [...updatedNotifications];
});
@@ -165,7 +180,6 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
.catch((e) => console.error(`Error marking all notifications read: ${e?.message || ""}`));
};
// TODO Tinker
useEffect(() => {
if (visible && !isConnected) {
refetch();
@@ -187,4 +201,8 @@ export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
);
}
export default connect((state) => ({ bodyshop: state.user.bodyshop }), null)(NotificationCenterContainer);
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
export default connect(mapStateToProps, null)(NotificationCenterContainer);