feature/IO-3096-GlobalNotifications - Checkpoint
This commit is contained in:
@@ -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")}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 64px;
|
top: 64px;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 600px;
|
//width: 600px;
|
||||||
background: #fff; /* White background, Ant’s default */
|
background: #fff; /* White background, Ant’s 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 */
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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`);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user