feature/IO-3096-GlobalNotifications - Checkpoint

This commit is contained in:
Dave Richer
2025-02-25 17:23:35 -05:00
parent 08b7f0e59c
commit c5d00f7641
9 changed files with 510 additions and 566 deletions

View File

@@ -1,13 +1,16 @@
// notification-center.container.jsx
import { useState, useEffect, useCallback } from "react";
import { useQuery, useMutation } 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";
export function NotificationCenterContainer({ visible, onClose }) {
export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
const [showUnreadOnly, setShowUnreadOnly] = useState(false);
const [notifications, setNotifications] = useState([]);
const [error, setError] = useState(null);
const { isConnected } = useSocket();
const {
data,
@@ -19,106 +22,115 @@ export function NotificationCenterContainer({ visible, onClose }) {
variables: {
limit: 20,
offset: 0,
where: showUnreadOnly ? { read: { _is_null: true } } : {} // Default to all notifications
where: showUnreadOnly ? { read: { _is_null: true } } : {}
},
fetchPolicy: "cache-and-network", // Ensure reactivity to cache updates
fetchPolicy: "cache-and-network",
notifyOnNetworkStatusChange: true,
pollInterval: isConnected ? 0 : 30000,
skip: false,
onError: (err) => {
setError(err.message);
console.error("GET_NOTIFICATIONS error:", err);
setTimeout(() => refetch(), 2000);
},
onCompleted: (data) => {
console.log("GET_NOTIFICATIONS completed:", data?.notifications);
}
});
const [markAllReadMutation, { error: mutationError }] = useMutation(MARK_ALL_NOTIFICATIONS_READ, {
update: (cache, { data: mutationData }) => {
const timestamp = new Date().toISOString();
cache.modify({
fields: {
notifications(existing = [], { readField }) {
return existing.map((notif) => {
if (readField("read", notif) === null) {
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: showUnreadOnly ? { read: { _is_null: true } } : {}
}
});
if (cachedNotifications?.notifications) {
cache.writeQuery({
query: GET_NOTIFICATIONS,
variables: {
limit: 20,
offset: 0,
where: showUnreadOnly ? { read: { _is_null: true } } : {}
},
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);
}
});
useEffect(() => {
console.log(
"Notifications data updated:",
data?.notifications?.map((n) => ({
id: n.id,
read: n.read,
created_at: n.created_at,
associationid: n.associationid
}))
);
}, [data]);
const [markAllReadMutation, { error: mutationError }] = useMutation(MARK_ALL_NOTIFICATIONS_READ, {
update: (cache) => {
cache.modify({
fields: {
notifications(existing = []) {
return existing.map((notif) => ({
...notif,
read: notif.read || new Date().toISOString()
}));
},
notifications_aggregate() {
return { aggregate: { count: 0 } };
}
}
});
},
onError: (err) => setError(err.message)
});
// Remove refetchNotifications function and useEffect/context logic
useEffect(() => {
console.log("Data changed:", data);
if (data?.notifications) {
const processedNotifications = data.notifications
.map((notif) => {
let scenarioText;
let scenarioMeta;
try {
scenarioText =
typeof notif.scenario_text === "string" ? JSON.parse(notif.scenario_text) : notif.scenario_text || [];
scenarioMeta =
typeof notif.scenario_meta === "string" ? JSON.parse(notif.scenario_meta) : notif.scenario_meta || [];
scenarioText = notif.scenario_text ? JSON.parse(notif.scenario_text) : [];
scenarioMeta = notif.scenario_meta ? JSON.parse(notif.scenario_meta) : {};
} catch (e) {
console.error("Error parsing JSON for notification:", notif.id, e);
scenarioText = [notif.fcm_text || "Invalid notification data"];
scenarioMeta = [];
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
if (!Array.isArray(scenarioMeta)) scenarioMeta = [scenarioMeta];
console.log("Processed notification:", {
id: notif.id,
scenarioText,
scenarioMeta,
read: notif.read,
created_at: notif.created_at,
associationid: notif.associationid,
raw: notif
});
return {
const processed = {
id: notif.id,
jobid: notif.jobid,
associationid: notif.associationid,
scenarioText,
scenarioMeta,
roNumber, // Add RO number to notification object
created_at: notif.created_at,
read: notif.read,
__typename: notif.__typename
};
console.log("Processed notification with RO:", processed);
return processed;
})
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); // Explicitly sort by created_at desc
console.log(
"Processed Notifications:",
processedNotifications.map((n) => ({
id: n.id,
read: n.read,
created_at: n.created_at,
associationid: n.associationid
}))
);
console.log("Number of notifications to render:", processedNotifications.length);
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
console.log("Setting notifications with RO:", processedNotifications);
setNotifications(processedNotifications);
setError(null);
} else {
console.log("No data yet or error in data:", data, queryError);
console.log("No notifications in data:", data);
}
}, [data, queryError]);
}, [data]);
useEffect(() => {
if (queryError || mutationError) {
@@ -126,56 +138,61 @@ export function NotificationCenterContainer({ visible, onClose }) {
}
}, [queryError, mutationError]);
useEffect(() => {
console.log("Notifications state updated:", notifications);
}, [notifications]);
const loadMore = useCallback(() => {
if (!loading && data?.notifications.length) {
console.log("Loading more notifications, current length:", data.notifications.length);
fetchMore({
variables: { offset: data.notifications.length },
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
console.log("Fetched more:", fetchMoreResult.notifications);
return {
notifications: [...prev.notifications, ...fetchMoreResult.notifications]
};
}
}).catch((err) => setError(err.message));
}).catch((err) => {
setError(err.message);
console.error("Fetch more error:", err);
});
}
}, [data?.notifications?.length, fetchMore, loading]);
const handleToggleUnreadOnly = (value) => {
setShowUnreadOnly(value);
console.log("Toggled showUnreadOnly:", value);
};
const handleMarkAllRead = () => {
markAllReadMutation().catch((e) =>
console.error(`Something went wrong marking all notifications read: ${e?.message || ""}`)
);
console.log("Marking all notifications as read");
markAllReadMutation()
.then(() => {
const timestamp = new Date().toISOString();
setNotifications((prev) => {
const updatedNotifications = prev.map((notif) =>
notif.read === null ? { ...notif, read: timestamp } : notif
);
console.log("Updated notifications after mark all read:", updatedNotifications);
return [...updatedNotifications];
});
})
.catch((e) => console.error(`Error marking all notifications read: ${e?.message || ""}`));
};
useEffect(() => {
if (visible) {
const virtuosoRef = { current: null };
virtuosoRef.current?.scrollTo({ top: 0 });
const handleScroll = () => {
if (virtuosoRef.current && virtuosoRef.current.scrollTop + 400 >= virtuosoRef.current.scrollHeight) {
loadMore();
}
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
if (visible && !isConnected) {
console.log("Notification Center opened, socket disconnected, refetching...");
refetch();
} else if (visible && isConnected) {
console.log("Notification Center opened, socket connected, relying on cache...");
refetch(); // Ensure fresh data even with socket
}
}, [visible, loading, data, loadMore]);
console.log(
"Rendering NotificationCenter with notifications:",
notifications.length,
notifications.map((n) => ({
id: n.id,
read: n.read,
created_at: n.created_at
}))
);
}, [visible, isConnected, refetch]);
return (
// Remove NotificationContext.Provider
<NotificationCenterComponent
visible={visible}
onClose={onClose}
@@ -185,8 +202,9 @@ export function NotificationCenterContainer({ visible, onClose }) {
showUnreadOnly={showUnreadOnly}
toggleUnreadOnly={handleToggleUnreadOnly}
markAllRead={handleMarkAllRead}
loadMore={loadMore}
/>
);
}
export default connect(null, null)(NotificationCenterContainer);
export default connect((state) => ({ bodyshop: state.user.bodyshop }), null)(NotificationCenterContainer);