feature/IO-3096-GlobalNotifications - Checkpoint - Notification Center
This commit is contained in:
@@ -23,6 +23,8 @@ import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
|||||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||||
import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx";
|
import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx";
|
||||||
import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx";
|
import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import { useSubscription, useApolloClient, gql } from "@apollo/client";
|
||||||
|
import { SUBSCRIBE_TO_NOTIFICATIONS } from "../graphql/notifications.queries.js";
|
||||||
|
|
||||||
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
||||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||||
@@ -46,6 +48,7 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
const client = useSplitClient().client;
|
const client = useSplitClient().client;
|
||||||
const [listenersAdded, setListenersAdded] = useState(false);
|
const [listenersAdded, setListenersAdded] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const apolloClient = useApolloClient();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!navigator.onLine) {
|
if (!navigator.onLine) {
|
||||||
@@ -104,6 +107,114 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
}
|
}
|
||||||
}, [bodyshop, client, currentUser.authorized]);
|
}, [bodyshop, client, currentUser.authorized]);
|
||||||
|
|
||||||
|
// Add subscription for all unread notifications with proper normalization and format
|
||||||
|
useSubscription(SUBSCRIBE_TO_NOTIFICATIONS, {
|
||||||
|
onData: ({ data }) => {
|
||||||
|
if (data.data?.notifications) {
|
||||||
|
console.log("Subscription data received (all unread):", data.data.notifications);
|
||||||
|
const newNotifs = data.data.notifications.filter(
|
||||||
|
(newNotif) =>
|
||||||
|
!apolloClient.cache
|
||||||
|
.readQuery({
|
||||||
|
query: gql`
|
||||||
|
query GetNotifications {
|
||||||
|
notifications(order_by: { created_at: desc }) {
|
||||||
|
id
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
?.notifications.some((n) => n.id === newNotif.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newNotifs.length === 0) return;
|
||||||
|
|
||||||
|
// Use writeQuery to normalize and add new notifications as References, matching cache format
|
||||||
|
apolloClient.cache.writeQuery({
|
||||||
|
query: gql`
|
||||||
|
query GetNotifications {
|
||||||
|
notifications(order_by: { created_at: desc }) {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
jobid
|
||||||
|
associationid
|
||||||
|
scenario_text
|
||||||
|
fcm_text
|
||||||
|
scenario_meta
|
||||||
|
created_at
|
||||||
|
read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
data: {
|
||||||
|
notifications: [
|
||||||
|
...newNotifs.map((notif) => ({
|
||||||
|
...notif,
|
||||||
|
__typename: "notifications",
|
||||||
|
scenario_text: JSON.stringify(
|
||||||
|
typeof notif.scenario_text === "string" ? JSON.parse(notif.scenario_text) : notif.scenario_text || []
|
||||||
|
),
|
||||||
|
scenario_meta: JSON.stringify(
|
||||||
|
typeof notif.scenario_meta === "string" ? JSON.parse(notif.scenario_meta) : notif.scenario_meta || []
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
...(apolloClient.cache.readQuery({
|
||||||
|
query: gql`
|
||||||
|
query GetNotifications {
|
||||||
|
notifications(order_by: { created_at: desc }) {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
jobid
|
||||||
|
associationid
|
||||||
|
scenario_text
|
||||||
|
fcm_text
|
||||||
|
scenario_meta
|
||||||
|
created_at
|
||||||
|
read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
})?.notifications || [])
|
||||||
|
].sort((a, b) => new Date(b.created_at) - new Date(a.created_at)) // Sort by created_at desc
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update notifications_aggregate for unread count
|
||||||
|
apolloClient.cache.modify({
|
||||||
|
id: "ROOT_QUERY",
|
||||||
|
fields: {
|
||||||
|
notifications_aggregate(existing = { aggregate: { count: 0 } }) {
|
||||||
|
const unreadCount = existing.aggregate.count + newNotifs.length;
|
||||||
|
console.log("Updating unread count from subscription:", unreadCount);
|
||||||
|
return {
|
||||||
|
...existing,
|
||||||
|
aggregate: {
|
||||||
|
...existing.aggregate,
|
||||||
|
count: unreadCount
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optimistic: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
console.error("Subscription error:", err);
|
||||||
|
// Fallback: Poll for all unread notifications if subscription fails
|
||||||
|
apolloClient
|
||||||
|
.query({
|
||||||
|
query: SUBSCRIBE_TO_NOTIFICATIONS,
|
||||||
|
variables: { where: { read: { _is_null: true } }, order_by: { created_at: "desc" } },
|
||||||
|
pollInterval: 30000
|
||||||
|
})
|
||||||
|
.catch((pollError) => console.error("Polling error:", pollError));
|
||||||
|
},
|
||||||
|
shouldResubscribe: true,
|
||||||
|
skip: !currentUser.authorized // Skip if user isn’t authorized
|
||||||
|
});
|
||||||
|
|
||||||
if (currentUser.authorized === null) {
|
if (currentUser.authorized === null) {
|
||||||
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,12 +137,32 @@ function Header({
|
|||||||
|
|
||||||
const [notificationVisible, setNotificationVisible] = useState(false);
|
const [notificationVisible, setNotificationVisible] = useState(false);
|
||||||
|
|
||||||
const { data: unreadData } = useQuery(GET_UNREAD_COUNT, {
|
const {
|
||||||
fetchPolicy: "cache-and-network"
|
data: unreadData,
|
||||||
|
error: unreadError,
|
||||||
|
refetch: refetchUnread,
|
||||||
|
loading: unreadLoading
|
||||||
|
} = useQuery(GET_UNREAD_COUNT, {
|
||||||
|
fetchPolicy: "network-only", // Force network request for fresh data
|
||||||
|
pollInterval: 30000, // Poll every 30 seconds to ensure updates
|
||||||
|
onError: (err) => {
|
||||||
|
console.error("Error fetching unread count:", err);
|
||||||
|
console.log("Unread data state:", unreadData, "Error details:", err);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const unreadCount = unreadData?.notifications_aggregate?.aggregate?.count || 0;
|
const unreadCount = unreadData?.notifications_aggregate?.aggregate?.count || 0;
|
||||||
|
|
||||||
|
// Refetch unread count when the component mounts, updates, or on specific events
|
||||||
|
useEffect(() => {
|
||||||
|
refetchUnread();
|
||||||
|
}, [refetchUnread, bodyshop, currentUser]); // Add dependencies to trigger refetch on user or shop changes
|
||||||
|
|
||||||
|
// Log unread count for debugging
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("Unread count updated:", unreadCount, "Loading:", unreadLoading, "Error:", unreadError);
|
||||||
|
}, [unreadCount, unreadLoading, unreadError]);
|
||||||
|
|
||||||
const handleNotificationClick = (e) => {
|
const handleNotificationClick = (e) => {
|
||||||
setNotificationVisible(!notificationVisible);
|
setNotificationVisible(!notificationVisible);
|
||||||
if (handleMenuClick) handleMenuClick(e);
|
if (handleMenuClick) handleMenuClick(e);
|
||||||
@@ -669,7 +689,7 @@ function Header({
|
|||||||
{
|
{
|
||||||
key: "notifications",
|
key: "notifications",
|
||||||
icon: (
|
icon: (
|
||||||
<Badge count={unreadCount} offset={[10, 0]}>
|
<Badge count={unreadCount}>
|
||||||
<BellFilled />
|
<BellFilled />
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ const NotificationCenterComponent = ({
|
|||||||
console.log(`Rendering notification ${index}:`, {
|
console.log(`Rendering notification ${index}:`, {
|
||||||
id: notification.id,
|
id: notification.id,
|
||||||
scenarioTextLength: notification.scenarioText.length,
|
scenarioTextLength: notification.scenarioText.length,
|
||||||
key: `${notification.id}-${index}`
|
read: notification.read,
|
||||||
|
created_at: notification.created_at,
|
||||||
|
associationid: notification.associationid // Log associationid for debugging
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<List.Item
|
<List.Item
|
||||||
@@ -45,11 +47,16 @@ const NotificationCenterComponent = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("Rendering NotificationCenter with notifications:", {
|
console.log(
|
||||||
count: notifications.length,
|
"Rendering NotificationCenter with notifications:",
|
||||||
ids: notifications.map((n) => n.id),
|
notifications.length,
|
||||||
totalCount: notifications.length
|
notifications.map((n) => ({
|
||||||
});
|
id: n.id,
|
||||||
|
read: n.read,
|
||||||
|
created_at: n.created_at,
|
||||||
|
associationid: n.associationid
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`notification-center ${visible ? "visible" : ""}`}>
|
<div className={`notification-center ${visible ? "visible" : ""}`}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { useQuery, useMutation } from "@apollo/client";
|
import { useQuery, useMutation } from "@apollo/client";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import NotificationCenterComponent from "./notification-center.component";
|
import NotificationCenterComponent from "./notification-center.component";
|
||||||
@@ -19,9 +19,9 @@ export function NotificationCenterContainer({ visible, onClose }) {
|
|||||||
variables: {
|
variables: {
|
||||||
limit: 20,
|
limit: 20,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
where: showUnreadOnly ? { read: { _is_null: true } } : {}
|
where: showUnreadOnly ? { read: { _is_null: true } } : {} // Default to all notifications
|
||||||
},
|
},
|
||||||
fetchPolicy: "cache-and-network",
|
fetchPolicy: "cache-and-network", // Ensure reactivity to cache updates
|
||||||
notifyOnNetworkStatusChange: true,
|
notifyOnNetworkStatusChange: true,
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
@@ -48,39 +48,69 @@ export function NotificationCenterContainer({ visible, onClose }) {
|
|||||||
onError: (err) => setError(err.message)
|
onError: (err) => setError(err.message)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Remove refetchNotifications function and useEffect/context logic
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.notifications) {
|
if (data?.notifications) {
|
||||||
const processedNotifications = data.notifications.map((notif) => {
|
const processedNotifications = data.notifications.map((notif) => {
|
||||||
let scenarioText;
|
let scenarioText;
|
||||||
|
let scenarioMeta;
|
||||||
try {
|
try {
|
||||||
scenarioText =
|
scenarioText =
|
||||||
typeof notif.scenario_text === "string" ? JSON.parse(notif.scenario_text) : notif.scenario_text || [];
|
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 || [];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Error parsing JSON for notification:", notif.id, e);
|
console.error("Error parsing JSON for notification:", notif.id, e);
|
||||||
scenarioText = [notif.fcm_text || "Invalid notification data"];
|
scenarioText = [notif.fcm_text || "Invalid notification data"];
|
||||||
|
scenarioMeta = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Array.isArray(scenarioText)) scenarioText = [scenarioText];
|
if (!Array.isArray(scenarioText)) scenarioText = [scenarioText];
|
||||||
|
if (!Array.isArray(scenarioMeta)) scenarioMeta = [scenarioMeta];
|
||||||
|
|
||||||
|
console.log("Processed notification:", {
|
||||||
|
id: notif.id,
|
||||||
|
scenarioText,
|
||||||
|
scenarioMeta,
|
||||||
|
read: notif.read,
|
||||||
|
created_at: notif.created_at,
|
||||||
|
raw: notif // Log raw data for debugging
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
id: notif.id,
|
id: notif.id,
|
||||||
jobid: notif.jobid,
|
jobid: notif.jobid,
|
||||||
associationid: notif.associationid,
|
associationid: notif.associationid,
|
||||||
scenarioText,
|
scenarioText,
|
||||||
|
scenarioMeta, // Add scenarioMeta for completeness (optional for rendering)
|
||||||
created_at: notif.created_at,
|
created_at: notif.created_at,
|
||||||
read: notif.read,
|
read: notif.read,
|
||||||
__typename: notif.__typename
|
__typename: notif.__typename
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Processed Notifications:", processedNotifications);
|
console.log(
|
||||||
|
"Notifications data updated:",
|
||||||
|
data?.notifications?.map((n) => ({
|
||||||
|
id: n.id,
|
||||||
|
read: n.read,
|
||||||
|
created_at: n.created_at
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"Processed Notifications:",
|
||||||
|
processedNotifications.map((n) => ({
|
||||||
|
id: n.id,
|
||||||
|
read: n.read,
|
||||||
|
created_at: n.created_at
|
||||||
|
}))
|
||||||
|
);
|
||||||
console.log("Number of notifications to render:", processedNotifications.length);
|
console.log("Number of notifications to render:", processedNotifications.length);
|
||||||
setNotifications(processedNotifications);
|
setNotifications(processedNotifications);
|
||||||
setError(null);
|
setError(null);
|
||||||
} else {
|
} else {
|
||||||
console.log("No data yet or error in data:", data, queryError);
|
console.log("No data yet or error in data:", data, queryError);
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data, queryError]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (queryError || mutationError) {
|
if (queryError || mutationError) {
|
||||||
@@ -88,7 +118,7 @@ export function NotificationCenterContainer({ visible, onClose }) {
|
|||||||
}
|
}
|
||||||
}, [queryError, mutationError]);
|
}, [queryError, mutationError]);
|
||||||
|
|
||||||
const loadMore = () => {
|
const loadMore = useCallback(() => {
|
||||||
if (!loading && data?.notifications.length) {
|
if (!loading && data?.notifications.length) {
|
||||||
fetchMore({
|
fetchMore({
|
||||||
variables: { offset: data.notifications.length },
|
variables: { offset: data.notifications.length },
|
||||||
@@ -100,7 +130,7 @@ export function NotificationCenterContainer({ visible, onClose }) {
|
|||||||
}
|
}
|
||||||
}).catch((err) => setError(err.message));
|
}).catch((err) => setError(err.message));
|
||||||
}
|
}
|
||||||
};
|
}, [data?.notifications?.length, fetchMore, loading]);
|
||||||
|
|
||||||
const handleToggleUnreadOnly = (value) => {
|
const handleToggleUnreadOnly = (value) => {
|
||||||
setShowUnreadOnly(value);
|
setShowUnreadOnly(value);
|
||||||
@@ -122,11 +152,20 @@ export function NotificationCenterContainer({ visible, onClose }) {
|
|||||||
window.addEventListener("scroll", handleScroll);
|
window.addEventListener("scroll", handleScroll);
|
||||||
return () => window.removeEventListener("scroll", handleScroll);
|
return () => window.removeEventListener("scroll", handleScroll);
|
||||||
}
|
}
|
||||||
}, [visible, loading, data]);
|
}, [visible, loading, data, loadMore]);
|
||||||
|
|
||||||
console.log("Rendering NotificationCenter with notifications:", notifications.length, notifications);
|
console.log(
|
||||||
|
"Rendering NotificationCenter with notifications:",
|
||||||
|
notifications.length,
|
||||||
|
notifications.map((n) => ({
|
||||||
|
id: n.id,
|
||||||
|
read: n.read,
|
||||||
|
created_at: n.created_at
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
// Remove NotificationContext.Provider
|
||||||
<NotificationCenterComponent
|
<NotificationCenterComponent
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { auth } from "../../firebase/firebase.utils";
|
|||||||
import { store } from "../../redux/store";
|
import { store } from "../../redux/store";
|
||||||
import { addAlerts, setWssStatus } from "../../redux/application/application.actions";
|
import { addAlerts, setWssStatus } from "../../redux/application/application.actions";
|
||||||
import client from "../../utils/GraphQLClient";
|
import client from "../../utils/GraphQLClient";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../Notifications/notificationContext.jsx";
|
||||||
|
import { gql } from "@apollo/client";
|
||||||
|
|
||||||
const useSocket = (bodyshop) => {
|
const useSocket = (bodyshop) => {
|
||||||
const socketRef = useRef(null);
|
const socketRef = useRef(null);
|
||||||
@@ -79,39 +80,88 @@ const useSocket = (bodyshop) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleNotification = (data) => {
|
const handleNotification = (data) => {
|
||||||
const { jobId, bodyShopId, notifications, notificationId } = data;
|
const { jobId, bodyShopId, notificationId, associationId, notifications } = data; // Changed to associationId (capital I)
|
||||||
console.log("handleNotification - Received", { jobId, bodyShopId, notificationId, notifications });
|
console.log("handleNotification - Received", {
|
||||||
|
jobId,
|
||||||
|
bodyShopId,
|
||||||
|
notificationId,
|
||||||
|
associationId,
|
||||||
|
notifications
|
||||||
|
});
|
||||||
|
|
||||||
|
// Construct the notification object to match the cache/database format
|
||||||
const newNotification = {
|
const newNotification = {
|
||||||
|
__typename: "notifications",
|
||||||
id: notificationId,
|
id: notificationId,
|
||||||
jobid: jobId,
|
jobid: jobId,
|
||||||
associationid: null,
|
associationid: associationId || null, // Use associationId from socket, default to null if missing
|
||||||
scenario_text: notifications.map((notif) => notif.body),
|
scenario_text: JSON.stringify(notifications.map((notif) => notif.body)), // Stringify as in the cache
|
||||||
fcm_text: notifications.map((notif) => notif.body).join(". ") + ".",
|
fcm_text: notifications.map((notif) => notif.body).join(". ") + ".",
|
||||||
scenario_meta: notifications.map((notif) => notif.variables || {}),
|
scenario_meta: JSON.stringify(notifications.map((notif) => notif.variables || {})), // Stringify as in the cache
|
||||||
created_at: new Date(notifications[0].timestamp).toISOString(),
|
created_at: new Date(notifications[0].timestamp).toISOString(),
|
||||||
read: null,
|
read: null // Assume unread unless specified otherwise
|
||||||
__typename: "notifications"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Use writeQuery to add the new notification to the cache, ensuring normalization and broadcasting
|
||||||
|
client.cache.writeQuery({
|
||||||
|
query: gql`
|
||||||
|
query GetNotifications {
|
||||||
|
notifications(order_by: { created_at: desc }) {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
jobid
|
||||||
|
associationid
|
||||||
|
scenario_text
|
||||||
|
fcm_text
|
||||||
|
scenario_meta
|
||||||
|
created_at
|
||||||
|
read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
data: {
|
||||||
|
notifications: [
|
||||||
|
newNotification,
|
||||||
|
...(
|
||||||
|
client.cache.readQuery({
|
||||||
|
query: gql`
|
||||||
|
query GetNotifications {
|
||||||
|
notifications(order_by: { created_at: desc }) {
|
||||||
|
__typename
|
||||||
|
id
|
||||||
|
jobid
|
||||||
|
associationid
|
||||||
|
scenario_text
|
||||||
|
fcm_text
|
||||||
|
scenario_meta
|
||||||
|
created_at
|
||||||
|
read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
})?.notifications || []
|
||||||
|
).filter((n) => n.id !== newNotification.id)
|
||||||
|
].sort((a, b) => new Date(b.created_at) - new Date(a.created_at)) // Sort by created_at desc
|
||||||
|
},
|
||||||
|
broadcast: true // Notify dependent queries of the cache update
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Cache updated with new notification:", newNotification);
|
||||||
|
|
||||||
|
// Update notifications_aggregate for unread count
|
||||||
client.cache.modify({
|
client.cache.modify({
|
||||||
id: "ROOT_QUERY",
|
id: "ROOT_QUERY",
|
||||||
fields: {
|
fields: {
|
||||||
notifications(existing = []) {
|
|
||||||
if (existing.some((n) => n.id === newNotification.id)) return existing;
|
|
||||||
console.log("Adding to cache", newNotification);
|
|
||||||
return [newNotification, ...existing];
|
|
||||||
},
|
|
||||||
notifications_aggregate(existing = { aggregate: { count: 0 } }) {
|
notifications_aggregate(existing = { aggregate: { count: 0 } }) {
|
||||||
// Fallback if aggregate isn’t in schema yet
|
const isUnread = newNotification.read === null;
|
||||||
if (!existing) return existing;
|
const countChange = isUnread ? 1 : 0;
|
||||||
console.log("Updating unread count", existing.aggregate.count + 1);
|
console.log("Updating unread count from socket:", existing.aggregate.count + countChange);
|
||||||
return {
|
return {
|
||||||
...existing,
|
...existing,
|
||||||
aggregate: {
|
aggregate: {
|
||||||
...existing.aggregate,
|
...existing.aggregate,
|
||||||
count: existing.aggregate.count + 1
|
count: existing.aggregate.count + countChange
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -183,7 +233,7 @@ const useSocket = (bodyshop) => {
|
|||||||
socketRef.current = null;
|
socketRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [bodyshop, client.cache]);
|
}, [bodyshop, notification]);
|
||||||
|
|
||||||
return { socket: socketRef.current, clientId };
|
return { socket: socketRef.current, clientId };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const MARK_ALL_NOTIFICATIONS_READ = gql`
|
|||||||
|
|
||||||
export const SUBSCRIBE_TO_NOTIFICATIONS = gql`
|
export const SUBSCRIBE_TO_NOTIFICATIONS = gql`
|
||||||
subscription SubscribeToNotifications {
|
subscription SubscribeToNotifications {
|
||||||
notifications(order_by: { created_at: desc }, limit: 1) {
|
notifications(where: { read: { _is_null: true } }, order_by: { created_at: desc }) {
|
||||||
id
|
id
|
||||||
jobid
|
jobid
|
||||||
associationid
|
associationid
|
||||||
|
|||||||
@@ -3776,7 +3776,11 @@
|
|||||||
"add-watchers": "Add Watchers",
|
"add-watchers": "Add Watchers",
|
||||||
"employee-search": "Search for an Employee",
|
"employee-search": "Search for an Employee",
|
||||||
"teams-search": "Search for a Team",
|
"teams-search": "Search for a Team",
|
||||||
"add-watchers-team": "Add Team Members"
|
"add-watchers-team": "Add Team Members",
|
||||||
|
"new-notification-title": "New Notification:",
|
||||||
|
"show-unread-only": "Show Unread",
|
||||||
|
"mark-all-read": "Mark Read",
|
||||||
|
"loading": "Loading Notifications..."
|
||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"remove": "remove"
|
"remove": "remove"
|
||||||
|
|||||||
@@ -4940,6 +4940,7 @@
|
|||||||
_eq: X-Hasura-User-Id
|
_eq: X-Hasura-User-Id
|
||||||
- active:
|
- active:
|
||||||
_eq: true
|
_eq: true
|
||||||
|
allow_aggregations: true
|
||||||
comment: ""
|
comment: ""
|
||||||
update_permissions:
|
update_permissions:
|
||||||
- role: user
|
- role: user
|
||||||
|
|||||||
@@ -200,25 +200,20 @@ const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => {
|
|||||||
// Emit notifications to users via Socket.io with notification ID
|
// Emit notifications to users via Socket.io with notification ID
|
||||||
for (const [user, bodyShopData] of Object.entries(allNotifications)) {
|
for (const [user, bodyShopData] of Object.entries(allNotifications)) {
|
||||||
const userMapping = await redisHelpers.getUserSocketMapping(user);
|
const userMapping = await redisHelpers.getUserSocketMapping(user);
|
||||||
logger.logger.debug(`User socket mapping for ${user}: ${JSON.stringify(userMapping)}`);
|
// Get all recipients for the user and extract the associationId (employeeId)
|
||||||
|
const userRecipients = recipients.filter((r) => r.user === user);
|
||||||
|
const associationId = userRecipients[0]?.employeeId;
|
||||||
|
|
||||||
for (const [bodyShopId, notifications] of Object.entries(bodyShopData)) {
|
for (const [bodyShopId, notifications] of Object.entries(bodyShopData)) {
|
||||||
const notificationId = notificationIdMap.get(`${user}:${bodyShopId}`);
|
const notificationId = notificationIdMap.get(`${user}:${bodyShopId}`);
|
||||||
if (userMapping && userMapping[bodyShopId]?.socketIds) {
|
if (userMapping && userMapping[bodyShopId]?.socketIds) {
|
||||||
userMapping[bodyShopId].socketIds.forEach((socketId) => {
|
userMapping[bodyShopId].socketIds.forEach((socketId) => {
|
||||||
logger.logger.debug(
|
|
||||||
`Emitting to socket ${socketId}: ${JSON.stringify({
|
|
||||||
jobId,
|
|
||||||
bodyShopId,
|
|
||||||
notifications,
|
|
||||||
notificationId
|
|
||||||
})}`
|
|
||||||
);
|
|
||||||
ioRedis.to(socketId).emit("notification", {
|
ioRedis.to(socketId).emit("notification", {
|
||||||
jobId,
|
jobId,
|
||||||
bodyShopId,
|
bodyShopId,
|
||||||
notifications,
|
notifications,
|
||||||
notificationId
|
notificationId,
|
||||||
|
associationId // now included in the emit payload
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
logger.logger.info(
|
logger.logger.info(
|
||||||
|
|||||||
Reference in New Issue
Block a user