Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import InactivityHandler from "./helper/inactivityhandler";
import Examination from "./Modules/Examination/examination";
import Database from "./Modules/Database/database";
import ProgrammeCurriculumRoutes from "./Modules/Program_curriculum/programmCurriculum";
import NotificationView from "./routes/notificationRoutes";
import NotFoundPage from "./components/NotFoundPage";

const theme = createTheme({
Expand Down Expand Up @@ -46,6 +47,14 @@ export default function App() {
</Layout>
}
/>
<Route
path="/dashboard/notifications"
element={
<Layout>
<NotificationView />
</Layout>
}
/>
<Route
path="/academics"
element={
Expand Down
14 changes: 7 additions & 7 deletions src/Modules/Dashboard/dashboardNotifications.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ function Dashboard() {
data: JSON.parse(item.data.replace(/'/g, '"')),
}));

const isAnnouncement = (item) =>
item?.data?.flag === "announcement" ||
item?.data?.type === "announcement";

setNotificationsList(
notificationsData.filter(
(item) => item.data?.flag !== "announcement",
),
notificationsData.filter((item) => !isAnnouncement(item)),
);
setAnnouncementsList(
notificationsData.filter(
(item) => item.data?.flag === "announcement",
),
notificationsData.filter((item) => isAnnouncement(item)),
);
} catch (error) {
console.error("Error fetching dashboard data:", error);
Expand Down Expand Up @@ -408,4 +408,4 @@ NotificationItem.propTypes = {
markAsUnread: PropTypes.func.isRequired,
deleteNotification: PropTypes.func.isRequired,
loading: PropTypes.number.isRequired,
};
};
81 changes: 81 additions & 0 deletions src/Modules/Notification/Announcements.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useEffect, useState } from "react";
import { notificationAPI, notificationUtils } from "./api";

function Announcements() {
const [announcementsList, setAnnouncementsList] = useState([]);
const [loading, setLoading] = useState(false);
const [loadingId, setLoadingId] = useState(-1);

// Fetch announcements on mount
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const data = await notificationAPI.fetchAll();
const { announcements } = notificationUtils.separate(data);
setAnnouncementsList(announcements);
} catch (err) {
console.error("Failed to fetch announcements:", err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);

// Mark as read
const handleMarkAsRead = async (announcementId) => {
try {
setLoadingId(announcementId);
await notificationAPI.markAsRead(announcementId);
setAnnouncementsList((prev) =>
prev.map((a) =>
a.id === announcementId ? { ...a, unread: false } : a,
),
);
} catch (err) {
console.error("Error:", err);
} finally {
setLoadingId(-1);
}
};

// Mark as unread
const handleMarkAsUnread = async (announcementId) => {
try {
setLoadingId(announcementId);
await notificationAPI.markAsUnread(announcementId);
setAnnouncementsList((prev) =>
prev.map((a) => (a.id === announcementId ? { ...a, unread: true } : a)),
);
} catch (err) {
console.error("Error:", err);
} finally {
setLoadingId(-1);
}
};

// Delete announcement
const handleDelete = async (announcementId) => {
try {
await notificationAPI.delete(announcementId);
setAnnouncementsList((prev) =>
prev.filter((a) => a.id !== announcementId),
);
} catch (err) {
console.error("Error:", err);
}
};

return {
announcementsList,
setAnnouncementsList,
loading,
loadingId,
markAsRead: handleMarkAsRead,
markAsUnread: handleMarkAsUnread,
deleteNotification: handleDelete,
};
}

export default Announcements;
77 changes: 77 additions & 0 deletions src/Modules/Notification/Notifications.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useEffect, useState } from "react";
import { notificationAPI, notificationUtils } from "./api";

function Notifications() {
const [notificationsList, setNotificationsList] = useState([]);
const [loading, setLoading] = useState(false);
const [loadingId, setLoadingId] = useState(-1);

// Fetch notifications on mount
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const data = await notificationAPI.fetchAll();
const { notifications } = notificationUtils.separate(data);
setNotificationsList(notifications);
} catch (err) {
console.error("Failed to fetch notifications:", err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);

// Mark as read
const handleMarkAsRead = async (notifId) => {
try {
setLoadingId(notifId);
await notificationAPI.markAsRead(notifId);
setNotificationsList((prev) =>
prev.map((n) => (n.id === notifId ? { ...n, unread: false } : n)),
);
} catch (err) {
console.error("Error:", err);
} finally {
setLoadingId(-1);
}
};

// Mark as unread
const handleMarkAsUnread = async (notifId) => {
try {
setLoadingId(notifId);
await notificationAPI.markAsUnread(notifId);
setNotificationsList((prev) =>
prev.map((n) => (n.id === notifId ? { ...n, unread: true } : n)),
);
} catch (err) {
console.error("Error:", err);
} finally {
setLoadingId(-1);
}
};

// Delete notification
const handleDelete = async (notifId) => {
try {
await notificationAPI.delete(notifId);
setNotificationsList((prev) => prev.filter((n) => n.id !== notifId));
} catch (err) {
console.error("Error:", err);
}
};

return {
notificationsList,
setNotificationsList,
loading,
loadingId,
markAsRead: handleMarkAsRead,
markAsUnread: handleMarkAsUnread,
deleteNotification: handleDelete,
};
}

export default Notifications;
106 changes: 106 additions & 0 deletions src/Modules/Notification/NotificationsPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { useMemo, useState } from "react";
import { Flex } from "@mantine/core";
import CustomBreadcrumbs from "../../components/Breadcrumbs.jsx";
import ModuleTabs from "../../components/moduleTabs.jsx";
import { useNotifications } from "./hooks/useNotifications";
import { sortNotifications, getUnreadCount } from "./utils/notificationUtils";
import NotificationList from "./components/NotificationList";
import NotificationFilters from "./components/NotificationFilters";

function NotificationsPage() {
const {
notificationsList,
announcementsList,
loading,
markAsRead,
markAsUnread,
deleteNotification,
} = useNotifications();

const [activeTab, setActiveTab] = useState("0");
const [sortedBy, setSortedBy] = useState("Most Recent");
const [loadingId, setLoadingId] = useState(-1);

// Tab configuration
const tabItems = [{ title: "Notifications" }, { title: "Announcements" }];

// Calculate badge counts
const notificationBadgeCount = getUnreadCount(notificationsList);
const announcementBadgeCount = getUnreadCount(announcementsList);
const badges = [notificationBadgeCount, announcementBadgeCount];

// Get current tab data
const notificationsToDisplay =
activeTab === "1" ? announcementsList : notificationsList;

// Sort notifications
const sortedNotifications = useMemo(
() => sortNotifications(notificationsToDisplay, sortedBy),
[sortedBy, notificationsToDisplay],
);

// Handlers with loading state management
const handleMarkAsRead = async (notifId) => {
try {
setLoadingId(notifId);
await markAsRead(notifId);
} finally {
setLoadingId(-1);
}
};

const handleMarkAsUnread = async (notifId) => {
try {
setLoadingId(notifId);
await markAsUnread(notifId);
} finally {
setLoadingId(-1);
}
};

const handleDelete = async (notifId) => {
try {
setLoadingId(notifId);
await deleteNotification(notifId);
} catch (err) {
console.error("Failed to delete:", err);
} finally {
setLoadingId(-1);
}
};

return (
<>
<CustomBreadcrumbs />

{/* Header with tabs and filters */}
<Flex
justify="space-between"
align={{ base: "start", sm: "center" }}
mt="lg"
direction={{ base: "column", sm: "row" }}
>
<ModuleTabs
tabs={tabItems}
activeTab={activeTab}
setActiveTab={setActiveTab}
badges={badges}
/>

<NotificationFilters sortedBy={sortedBy} onSortChange={setSortedBy} />
</Flex>

{/* Notifications list */}
<NotificationList
notifications={sortedNotifications}
onMarkAsRead={handleMarkAsRead}
onMarkAsUnread={handleMarkAsUnread}
onDelete={handleDelete}
loading={loading}
loadingId={loadingId}
/>
</>
);
}

export default NotificationsPage;
Loading