Files
bodyshop/client/src/components/notification-center/notification-center.container.jsx

181 lines
6.1 KiB
JavaScript

import { useCallback, useEffect, useMemo, useState } from "react";
import { useQuery } from "@apollo/client";
import { connect } from "react-redux";
import NotificationCenterComponent from "./notification-center.component";
import { GET_NOTIFICATIONS } from "../../graphql/notifications.queries";
import { INITIAL_NOTIFICATIONS, useSocket } from "../../contexts/SocketIO/socketContext.jsx";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors.js";
import day from "../../utils/day.js";
// This will be used to poll for notifications when the socket is disconnected
const NOTIFICATION_POLL_INTERVAL_SECONDS = 60;
export function NotificationCenterContainer({ visible, onClose, bodyshop }) {
const [showUnreadOnly, setShowUnreadOnly] = useState(false);
const [notifications, setNotifications] = useState([]);
const [error, setError] = useState(null);
const { isConnected, markNotificationRead, markAllNotificationsRead } = 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,
loading,
error: queryError,
refetch
} = useQuery(GET_NOTIFICATIONS, {
variables: {
limit: INITIAL_NOTIFICATIONS,
offset: 0,
where: whereClause
},
fetchPolicy: "cache-and-network",
notifyOnNetworkStatusChange: true,
pollInterval: isConnected ? 0 : day.duration(NOTIFICATION_POLL_INTERVAL_SECONDS, "seconds").asMilliseconds(),
skip: !userAssociationId,
onError: (err) => {
setError(err.message);
console.error(`Error polling Notifications in notification-center: ${err?.message || ""}`);
setTimeout(() => refetch(), day.duration(2, "seconds").asMilliseconds());
}
});
useEffect(() => {
if (data?.notifications) {
const processedNotifications = data.notifications
.map((notif) => {
let scenarioText;
let scenarioMeta;
try {
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 = {};
}
if (!Array.isArray(scenarioText)) scenarioText = [scenarioText];
const roNumber = notif.job.ro_number;
if (!Array.isArray(scenarioMeta)) scenarioMeta = [scenarioMeta];
return {
id: notif.id,
jobid: notif.jobid,
associationid: notif.associationid,
scenarioText,
scenarioMeta,
roNumber,
created_at: notif.created_at,
read: notif.read,
__typename: notif.__typename
};
})
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
setNotifications(processedNotifications);
setError(null);
}
}, [data]);
useEffect(() => {
if (queryError) {
setError(queryError.message);
}
}, [queryError]);
const loadMore = useCallback(() => {
if (!loading && data?.notifications.length) {
fetchMore({
variables: { offset: data.notifications.length, where: whereClause },
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return {
notifications: [...prev.notifications, ...fetchMoreResult.notifications]
};
}
}).catch((err) => {
setError(err.message);
console.error("Fetch more error:", err);
});
}
}, [data?.notifications?.length, fetchMore, loading, whereClause]);
const handleToggleUnreadOnly = (value) => {
setShowUnreadOnly(value);
};
const handleMarkAllRead = useCallback(() => {
markAllNotificationsRead()
.then(() => {
const timestamp = new Date().toISOString();
setNotifications((prev) => {
const updatedNotifications = prev.map((notif) =>
notif.read === null && notif.associationid === userAssociationId
? {
...notif,
read: timestamp
}
: notif
);
return [...updatedNotifications];
});
})
.catch((e) => console.error(`Error marking all notifications read: ${e?.message || ""}`));
}, [markAllNotificationsRead, userAssociationId]);
const handleNotificationClick = useCallback(
(notificationId) => {
markNotificationRead({
variables: { id: notificationId }
})
.then(() => {
const timestamp = new Date().toISOString();
setNotifications((prev) => {
return prev.map((notif) =>
notif.id === notificationId && !notif.read ? { ...notif, read: timestamp } : notif
);
});
})
.catch((e) => console.error(`Error marking notification read: ${e?.message || ""}`));
},
[markNotificationRead]
);
useEffect(() => {
if (visible && !isConnected) {
refetch().catch(
(err) => `Something went wrong re-fetching notifications in the notification-center: ${err?.message || ""}`
);
}
}, [visible, isConnected, refetch]);
return (
<NotificationCenterComponent
visible={visible}
onClose={onClose}
notifications={notifications}
loading={loading}
error={error}
showUnreadOnly={showUnreadOnly}
toggleUnreadOnly={handleToggleUnreadOnly}
markAllRead={handleMarkAllRead}
loadMore={loadMore}
onNotificationClick={handleNotificationClick}
/>
);
}
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
export default connect(mapStateToProps, null)(NotificationCenterContainer);