feature/IO-3096-GlobalNotifications - Checkpoint

This commit is contained in:
Dave Richer
2025-02-25 19:58:00 -05:00
parent c5d00f7641
commit fa39e2b97e
5 changed files with 35 additions and 26 deletions

View File

@@ -22,7 +22,6 @@ const NotificationCenterComponent = ({
const { t } = useTranslation(); const { t } = useTranslation();
const renderNotification = (index, notification) => { const renderNotification = (index, notification) => {
console.log("Rendering notification at index:", index, notification);
return ( return (
<List.Item <List.Item
key={`${notification.id}-${index}`} key={`${notification.id}-${index}`}
@@ -58,12 +57,10 @@ const NotificationCenterComponent = ({
); );
}; };
console.log("NotificationCenterComponent render:", { notifications, loading, error, showUnreadOnly });
return ( return (
<div className={`notification-center ${visible ? "visible" : ""}`}> <div className={`notification-center ${visible ? "visible" : ""}`}>
<div className="notification-header"> <div className="notification-header">
<h3>{t("notifications.labels.new-notification-title")}</h3> <h3>{t("notifications.labels.notification-center")}</h3>
<div className="notification-controls"> <div className="notification-controls">
<Checkbox checked={showUnreadOnly} onChange={(e) => toggleUnreadOnly(e.target.checked)}> <Checkbox checked={showUnreadOnly} onChange={(e) => toggleUnreadOnly(e.target.checked)}>
{t("notifications.labels.show-unread-only")} {t("notifications.labels.show-unread-only")}

View File

@@ -2,7 +2,7 @@
position: absolute; position: absolute;
top: 64px; top: 64px;
right: 0; right: 0;
width: 600px; //width: 600px;
background: #fff; /* White background, Ants default */ background: #fff; /* White background, Ants default */
color: rgba(0, 0, 0, 0.85); /* Primary text color in Ant 5 */ color: rgba(0, 0, 0, 0.85); /* Primary text color in Ant 5 */
border: 1px solid #d9d9d9; /* Neutral gray border */ border: 1px solid #d9d9d9; /* Neutral gray border */

View File

@@ -42,7 +42,6 @@ export const SocketProvider = ({ children, bodyshop }) => {
break; break;
} }
if (!import.meta.env.DEV) return; if (!import.meta.env.DEV) return;
console.log(`Received message for bodyshop ${bodyshop.id}:`, message);
}; };
const handleConnect = () => { const handleConnect = () => {
@@ -50,7 +49,6 @@ export const SocketProvider = ({ children, bodyshop }) => {
setClientId(socketInstance.id); setClientId(socketInstance.id);
setIsConnected(true); setIsConnected(true);
store.dispatch(setWssStatus("connected")); store.dispatch(setWssStatus("connected"));
console.log("Socket connected, ID:", socketInstance.id);
}; };
const handleReconnect = () => { const handleReconnect = () => {
@@ -87,24 +85,19 @@ export const SocketProvider = ({ children, bodyshop }) => {
}; };
const handleNotification = (data) => { const handleNotification = (data) => {
const { jobId, bodyShopId, notificationId, associationId, notifications } = data; const { jobId, jobRoNumber, notificationId, associationId, notifications } = data;
console.log("Socket Notification Received (ID:", notificationId, "):", {
jobId,
bodyShopId,
associationId,
notifications
});
const newNotification = { const newNotification = {
__typename: "notifications", __typename: "notifications",
id: notificationId, id: notificationId,
jobid: jobId, jobid: jobId,
associationid: associationId || null, associationid: associationId,
scenario_text: JSON.stringify(notifications.map((notif) => notif.body)), scenario_text: JSON.stringify(notifications.map((notif) => notif.body)),
fcm_text: notifications.map((notif) => notif.body).join(". ") + ".", fcm_text: notifications.map((notif) => notif.body).join(". ") + ".",
scenario_meta: JSON.stringify(notifications.map((notif) => notif.variables || {})), scenario_meta: JSON.stringify(notifications.map((notif) => notif.variables || {})),
created_at: new Date(notifications[0].timestamp).toISOString(), created_at: new Date(notifications[0].timestamp).toISOString(),
read: null read: null,
job: { ro_number: jobRoNumber }
}; };
try { try {
@@ -122,13 +115,15 @@ export const SocketProvider = ({ children, bodyshop }) => {
scenario_meta scenario_meta
created_at created_at
read read
job {
ro_number
}
} }
} }
` `
})?.notifications || []; })?.notifications || [];
if (existingNotifications.some((n) => n.id === newNotification.id)) { if (existingNotifications.some((n) => n.id === newNotification.id)) {
console.log("Duplicate notification detected, skipping:", notificationId);
return; return;
} }
@@ -145,6 +140,9 @@ export const SocketProvider = ({ children, bodyshop }) => {
scenario_meta scenario_meta
created_at created_at
read read
job {
ro_number
}
} }
} }
`, `,
@@ -156,15 +154,12 @@ export const SocketProvider = ({ children, bodyshop }) => {
broadcast: true broadcast: true
}); });
console.log("Cache updated with new notification:", newNotification);
client.cache.modify({ client.cache.modify({
id: "ROOT_QUERY", id: "ROOT_QUERY",
fields: { fields: {
notifications_aggregate(existing = { aggregate: { count: 0 } }) { notifications_aggregate(existing = { aggregate: { count: 0 } }) {
const isUnread = newNotification.read === null; const isUnread = newNotification.read === null;
const countChange = isUnread ? 1 : 0; const countChange = isUnread ? 1 : 0;
console.log("Updating unread count from socket:", existing.aggregate.count + countChange);
return { return {
...existing, ...existing,
aggregate: { aggregate: {
@@ -199,7 +194,6 @@ export const SocketProvider = ({ children, bodyshop }) => {
socketInstance.on("disconnect", handleDisconnect); socketInstance.on("disconnect", handleDisconnect);
socketInstance.on("bodyshop-message", handleBodyshopMessage); socketInstance.on("bodyshop-message", handleBodyshopMessage);
socketInstance.on("message", (message) => { socketInstance.on("message", (message) => {
console.log("Raw socket message:", message);
try { try {
if (typeof message === "string" && message.startsWith("42")) { if (typeof message === "string" && message.startsWith("42")) {
const parsedMessage = JSON.parse(message.slice(2)); const parsedMessage = JSON.parse(message.slice(2));

View File

@@ -77,11 +77,11 @@ const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => {
const addWorker = new Worker( const addWorker = new Worker(
"notificationsAdd", "notificationsAdd",
async (job) => { async (job) => {
const { jobId, key, variables, recipients, body } = job.data; const { jobId, key, variables, recipients, body, jobRoNumber } = job.data;
logger.logger.info(`Adding notifications for jobId ${jobId}`); logger.logger.info(`Adding notifications for jobId ${jobId}`);
const redisKeyPrefix = `app:notifications:${jobId}`; const redisKeyPrefix = `app:notifications:${jobId}`;
const notification = { key, variables, body, timestamp: Date.now() }; const notification = { key, variables, body, jobRoNumber, timestamp: Date.now() };
for (const recipient of recipients) { for (const recipient of recipients) {
const { user } = recipient; const { user } = recipient;
@@ -206,14 +206,17 @@ const loadAppQueue = async ({ pubClient, logger, redisHelpers, ioRedis }) => {
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}`);
const jobRoNumber = notifications[0]?.jobRoNumber;
if (userMapping && userMapping[bodyShopId]?.socketIds) { if (userMapping && userMapping[bodyShopId]?.socketIds) {
userMapping[bodyShopId].socketIds.forEach((socketId) => { userMapping[bodyShopId].socketIds.forEach((socketId) => {
ioRedis.to(socketId).emit("notification", { ioRedis.to(socketId).emit("notification", {
jobId, jobId,
jobRoNumber,
bodyShopId, bodyShopId,
notifications, notifications,
notificationId, notificationId,
associationId // now included in the emit payload associationId
}); });
}); });
logger.logger.info( logger.logger.info(
@@ -281,10 +284,10 @@ const dispatchAppsToQueue = async ({ appsToDispatch, logger }) => {
const appQueue = getQueue(); const appQueue = getQueue();
for (const app of appsToDispatch) { for (const app of appsToDispatch) {
const { jobId, bodyShopId, key, variables, recipients, body } = app; const { jobId, bodyShopId, key, variables, recipients, body, jobRoNumber } = app;
await appQueue.add( await appQueue.add(
"add-notification", "add-notification",
{ jobId, bodyShopId, key, variables, recipients, body }, { jobId, bodyShopId, key, variables, recipients, body, jobRoNumber },
{ jobId: `${jobId}:${Date.now()}` } { jobId: `${jobId}:${Date.now()}` }
); );
logger.logger.info(`Added notification to queue for jobId ${jobId} with ${recipients.length} recipients`); logger.logger.info(`Added notification to queue for jobId ${jobId} with ${recipients.length} recipients`);

View File

@@ -24,6 +24,7 @@ const alternateTransportChangedBuilder = (data) => {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
jobRoNumber: data.jobRoNumber,
key: "notifications.job.alternateTransportChanged", key: "notifications.job.alternateTransportChanged",
body, // Same as email body body, // Same as email body
variables: { variables: {
@@ -54,6 +55,7 @@ const billPostedHandler = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.billPosted", key: "notifications.job.billPosted",
body, body,
@@ -85,6 +87,7 @@ const criticalPartsStatusChangedBuilder = (data) => {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
jobRoNumber: data.jobRoNumber,
key: "notifications.job.criticalPartsStatusChanged", key: "notifications.job.criticalPartsStatusChanged",
body, body,
variables: { variables: {
@@ -116,6 +119,7 @@ const intakeDeliveryChecklistCompletedBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.checklistCompleted", key: "notifications.job.checklistCompleted",
body, body,
@@ -147,6 +151,7 @@ const jobAssignedToMeBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.assigned", key: "notifications.job.assigned",
body, body,
@@ -177,6 +182,7 @@ const jobsAddedToProductionBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.addedToProduction", key: "notifications.job.addedToProduction",
body, body,
@@ -205,6 +211,7 @@ const jobStatusChangeBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.statusChanged", key: "notifications.job.statusChanged",
body, body,
@@ -236,6 +243,7 @@ const newMediaAddedReassignedBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.newMediaAdded", key: "notifications.job.newMediaAdded",
body, body,
@@ -264,6 +272,7 @@ const newNoteAddedBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.newNoteAdded", key: "notifications.job.newNoteAdded",
body, body,
@@ -294,6 +303,7 @@ const newTimeTicketPostedBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.newTimeTicketPosted", key: "notifications.job.newTimeTicketPosted",
body, body,
@@ -322,6 +332,7 @@ const partMarkedBackOrderedBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.partBackOrdered", key: "notifications.job.partBackOrdered",
body, body,
@@ -353,6 +364,7 @@ const paymentCollectedCompletedBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.paymentCollected", key: "notifications.job.paymentCollected",
body, body,
@@ -383,6 +395,7 @@ const scheduledDatesChangedBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.scheduledDatesChanged", key: "notifications.job.scheduledDatesChanged",
body, body,
@@ -418,6 +431,7 @@ const supplementImportedBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: "notifications.job.supplementImported", key: "notifications.job.supplementImported",
body, body,
@@ -448,6 +462,7 @@ const tasksUpdatedCreatedBuilder = (data) => {
const result = { const result = {
app: { app: {
jobId: data.jobId, jobId: data.jobId,
jobRoNumber: data.jobRoNumber,
bodyShopId: data.bodyShopId, bodyShopId: data.bodyShopId,
key: data.isNew ? "notifications.job.taskCreated" : "notifications.job.taskUpdated", key: data.isNew ? "notifications.job.taskCreated" : "notifications.job.taskUpdated",
body, body,