Skip to content

Commit

Permalink
Merge pull request #305 from agiledev-students-fall2023/task/236/stud…
Browse files Browse the repository at this point in the history
…ent-side-notifications

Task/236/student side notifications
  • Loading branch information
hasiburratul authored Dec 5, 2023
2 parents f675755 + a1ea5f2 commit dfd3e7e
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 27 deletions.
2 changes: 1 addition & 1 deletion back-end/test/createIssue.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,4 @@ describe("Integration Tests for Create Issue Endpoint", () => {
});
});
});
/* eslint-enable */
/* eslint-enable */
39 changes: 39 additions & 0 deletions front-end/src/components/student/StudentNavbar/StudentNavbar.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@
height: 60px;
}

.notification-icon .notification-count {
position: relative;
bottom: 7px;
font: var(--primary-font);
font-weight: bold;
color: var(--background);
right: 24px;
text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; /* Simple black outline */

}

.student-info {
display: flex;
align-items: center;
Expand Down Expand Up @@ -76,8 +87,36 @@
padding: 15px; /* Slightly larger padding for content breathing space */
font-size: 0.9em; /* Adjust font size for better readability */
margin-top: 2%;
align-items: center;
max-height: 100%;
}

.notification-overlay .scrollable-content {
width: 100%; /* Adjust width as needed */
max-height: 450px; /* Adjust height to leave space for the button */
overflow-y: auto; /* Enable vertical scrolling */
}

.notification-overlay .load-more-button{
background-color: var(--secondary);
border: 1px solid #a8a8a8;
font: var(--secondary-font);
padding: 7px;
border-radius: 5px;
align-items: center;
margin-top: 10px;

}

.notification-overlay .issue-button{
padding: 7px;
}



.notification-overlay .load-more-button:hover {
background-color: #a8a8a8; /* Slightly darker shade on hover */
}


@media (max-width: 768px) {
Expand Down
107 changes: 86 additions & 21 deletions front-end/src/components/student/StudentNavbar/StudentNavbar.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,109 @@
import { useContext, useState } from "react";
import { useContext, useState, useEffect } from "react";
import { useNavigate } from 'react-router-dom';
import axios from "axios";
import logoutImage from "../../../assets/images/logout-icon.png";
import notificationIcon from "../../../assets/images/notification-icon.png";
import { AuthContext } from "../../general/AuthContext/AuthContext";
import "./StudentNavbar.css";

export default function StudentNavbar({ studentName }) {
const [notificationTimer, setNotificationTimer] = useState(null);
export default function StudentNavbar({ studentName, studentnetID, onIssueSelect }) {
const [showNotificationOverlay, setShowNotificationOverlay] = useState(false);
// const [showNotification, setShowNotification] = useState(false);
const { setIsAuthenticated } = useContext(AuthContext); // Use AuthContext
const [notificationData, setNotificationData] = useState(() => {
const savedNotifications = localStorage.getItem('notifications');
return savedNotifications ? JSON.parse(savedNotifications) : [];
});
const [displayedCount, setDisplayedCount] = useState(2);
const { setIsAuthenticated } = useContext(AuthContext);
const navigate = useNavigate();
const BASE_URL = process.env.REACT_APP_BACKEND_URL;
const actionRequiredCount = notificationData.filter(issue => issue.currentStatus === "Action Required").length;

// Handles notification click to display a notification overlay
const handleNotificationClick = () => {
setShowNotificationOverlay(true);
const loadMore = (event) => {
event.stopPropagation();
setDisplayedCount(prevCount => prevCount + 2); // load 2 more items
};

if (notificationTimer) {
clearTimeout(notificationTimer);
const handleIssueClick = (issueIndex) => {
if (onIssueSelect) {
onIssueSelect(issueIndex); // Call the passed function with the index of the clicked issue
}
// Timer for overlay
const timerId = setTimeout(() => {
setShowNotificationOverlay(false);
}, 5000);
};

setNotificationTimer(timerId);
const fetchNotificationData = async () => {
console.log("Fetching notification data...");
try {
const response = await axios.get(`${BASE_URL}/api/issues/student/${studentnetID}`, {
withCredentials: true
});
console.log("Fetched Notification Data:", response.data);
setNotificationData(response.data);
localStorage.setItem('notifications', JSON.stringify(response.data));
setShowNotificationOverlay(true);
} catch (error) {
console.error('Error fetching notification data:', error);
}
};

const oneMonthAgo = new Date(); // assuming relevant issues are the ones that recquire action from one month ago
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 5);
const parseDate = (dateStr) => {
const [day, month, year] = dateStr.split('/').map(Number);
return new Date(year, month - 1, day);
};

// TODO: Sync the notification overlay
const renderNotificationOverlay = () => {
if (showNotificationOverlay) {
const actionRequiredIssues = notificationData
.filter(issue => issue.currentStatus === "Action Required")
.sort((a, b) => parseDate(b.dateCreated) - parseDate(a.dateCreated))
.slice(0, displayedCount);

return (
<div className="notification-overlay">
<p>No new notifications</p>
<div className="notification-overlay" onClick={() => setShowNotificationOverlay(false)}>
<div className="scrollable-content">
{actionRequiredIssues.length > 0 ? (
actionRequiredIssues.map((issue, index) => (
<button className= "issue-button" key={index} onClick={() => handleIssueClick(issue.index)}>
<p><strong>Action Required: {issue.title}</strong></p>
</button>
))
) : (
<p>No action required notifications</p>
)}
</div>

{displayedCount === actionRequiredIssues.length && (
<button className="load-more-button" onClick={loadMore}>Load More</button>
)}
</div>
);
}
};

useEffect(() => {
const savedNotifications = localStorage.getItem('notifications');
if (savedNotifications) {
setNotificationData(JSON.parse(savedNotifications));
}

// Set interval for periodic updates
const intervalId = setInterval(fetchNotificationData, 60000);

const handleClickOutside = (event) => {
const overlay = document.querySelector('.notification-overlay');
if (showNotificationOverlay && overlay && !overlay.contains(event.target)) {
setShowNotificationOverlay(false);
}
};

document.addEventListener('mousedown', handleClickOutside);

return () => {
clearInterval(intervalId);
document.removeEventListener('mousedown', handleClickOutside);
};
}, [showNotificationOverlay]);

const handleLogout = () => {
axios.get(`${BASE_URL}/api/logout`, { withCredentials: true })
.then(() => {
Expand All @@ -56,9 +120,11 @@ export default function StudentNavbar({ studentName }) {
<p className="h1-student-dashboard">NYUAD ISSUE RESOLUTION</p>
<div className="student-info">
<span className="student-name">Hello, {studentName}</span>
<span className="notification-icon" onClick={handleNotificationClick}>
{/* 🔔 */}
<span className="notification-icon" onClick={fetchNotificationData}>
<img src={notificationIcon} alt="Notification" />
{actionRequiredCount > 0 && (
<span className="notification-count">{actionRequiredCount}</span>
)}
</span>
<img
src={logoutImage}
Expand All @@ -69,6 +135,5 @@ export default function StudentNavbar({ studentName }) {
</div>
{renderNotificationOverlay()}
</div>

);
}
15 changes: 10 additions & 5 deletions front-end/src/layouts/StudentDashboard/StudentDashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ const StudentDashboard = () => {
const [sortOrder, setSortOrder] = useState("latestFirst");
const [isCreateRequestVisible, setIsCreateRequestVisible] = useState(false);

// my additions
// end

const selectIssue = (issueIndex) => {
setRequest(issueIndex); // Assuming 'setRequest' updates the state to show issue details
setIsIssueOverlayOpen(true); // Open the overlay to show the issue details
};

// API
const BASE_URL = process.env.REACT_APP_BACKEND_URL;

Expand Down Expand Up @@ -543,11 +551,8 @@ const StudentDashboard = () => {

return (
<>
<div
className={`requests ${isIssueOverlayOpen || isCreateRequestVisible ? "blur-background" : ""}`}
>
<StudentNavbar studentName={userName} />

<div className={`requests ${isIssueOverlayOpen || isCreateRequestVisible ? "blur-background" : ""}`}>
<StudentNavbar studentName= {userName} studentnetID= {userNetID} onIssueSelect={selectIssue}/>
<h2 className="h2-student-dashboard">Your Requests</h2>
<div className="actions">
<div className="search-bar">
Expand Down

0 comments on commit dfd3e7e

Please sign in to comment.