diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json
index 44cca96..fa63377 100644
--- a/public/locales/de/translation.json
+++ b/public/locales/de/translation.json
@@ -392,5 +392,15 @@
},
"createEquipmentDocumentationModal": {
"buttonCreateDocumentation": "Dokumentation erstellen"
+ },
+ "header": {
+ "notificationDrawer": {
+ "title": "Benachrichtigungen",
+ "deleteAllButtonText": "Alle löschen",
+ "deleteAllPopconfirm": {
+ "title": "Sind Sie sicher, dass Sie alle Benachrichtigungen löschen möchten?"
+ },
+ "noNotifications": "Keine Benachrichtigungen"
+ }
}
}
diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json
index 580a30c..1502c93 100644
--- a/public/locales/en/translation.json
+++ b/public/locales/en/translation.json
@@ -391,5 +391,15 @@
},
"createEquipmentDocumentationModal": {
"buttonCreateDocumentation": "Create Documentation"
+ },
+ "header": {
+ "notificationDrawer": {
+ "title": "Notifications",
+ "deleteAllButtonText": "Delete All",
+ "deleteAllPopconfirm": {
+ "title": "Are you sure you want to delete all notifications?"
+ },
+ "noNotifications": "No notifications"
+ }
}
}
diff --git a/src/App.js b/src/App.js
index d753e68..996f6af 100644
--- a/src/App.js
+++ b/src/App.js
@@ -12,6 +12,7 @@ import { GroupTasksProvider } from "./Contexts/GroupTasksContext";
import { AdminAreaRolesProvider } from "./Contexts/AdminAreaRolesContext";
import { UserProfileProvider } from "./Contexts/UserProfileContext";
import { UsersProvider } from "./Contexts/UsersContext";
+import HeaderProvider from "./Contexts/HeaderContext";
export default function App() {
const [notificationApi, notificationContextHolder] =
@@ -35,30 +36,32 @@ export default function App() {
{notificationContextHolder}
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
+ isWebSocketReady={isWebSocketReady}
+ setIsWebSocketReady={setIsWebSocketReady}
+ notificationApi={notificationApi}
+ >
+
+
+
+
+
+
+
+
+
+
);
diff --git a/src/Components/Header/index.js b/src/Components/Header/index.js
new file mode 100644
index 0000000..e6cfb6a
--- /dev/null
+++ b/src/Components/Header/index.js
@@ -0,0 +1,165 @@
+import {
+ BellOutlined,
+ CheckCircleOutlined,
+ CheckOutlined,
+ CloseCircleOutlined,
+ CloseOutlined,
+ DeleteOutlined,
+ ExclamationCircleOutlined,
+ InboxOutlined,
+ InfoCircleOutlined,
+ MenuFoldOutlined,
+ MenuUnfoldOutlined,
+ QuestionCircleOutlined,
+} from "@ant-design/icons";
+import { Badge, Button, Drawer, List, Popconfirm, Typography } from "antd";
+import { Header } from "antd/es/layout/layout";
+import { useEffect, useState } from "react";
+import { useHeaderContext } from "../../Contexts/HeaderContext";
+import { FormatDatetime, myFetch } from "../../utils";
+import { useWebSocketContext } from "../../Contexts/WebSocketContext";
+import { SentMessagesCommands } from "../../Handlers/WebSocketMessageHandler";
+import { useTranslation } from "react-i18next";
+
+export default function HeaderMenu({
+ isSideMenuCollapsed,
+ setIsSideMenuCollapsed,
+}) {
+ const webSocketContext = useWebSocketContext();
+ const headerContext = useHeaderContext();
+ const { t } = useTranslation();
+ const [isNotificationDrawerOpen, setIsNotificationDrawerOpen] =
+ useState(false);
+
+ useEffect(() => {
+ // fetch will only be called if the drawer is open and there are no notifications
+ // further notifications will be fetched by the websocket
+ if (!isNotificationDrawerOpen || headerContext.notifications !== null)
+ return;
+
+ myFetch("/notifications", "GET").then((data) =>
+ headerContext.setNotifications(data.Notifications)
+ );
+ }, [isNotificationDrawerOpen]);
+
+ return (
+
+ );
+}
+
+function NotificationTypeIcon({ type }) {
+ switch (type) {
+ case 1:
+ return ;
+ case 2:
+ return ;
+ case 3:
+ return (
+
+ );
+ case 4:
+ return ;
+ default:
+ return ;
+ }
+}
diff --git a/src/Components/PageContent/index.js b/src/Components/PageContent/index.js
index e1c0c31..10eff7b 100644
--- a/src/Components/PageContent/index.js
+++ b/src/Components/PageContent/index.js
@@ -1,7 +1,7 @@
-import { Content, Header } from "antd/es/layout/layout";
+import { Content } from "antd/es/layout/layout";
import AppRoutes from "../AppRoutes";
-import { Button, Layout } from "antd";
-import { MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons";
+import { Layout } from "antd";
+import HeaderMenu from "../Header";
export default function PageContent({
isSideMenuCollapsed,
@@ -9,29 +9,10 @@ export default function PageContent({
}) {
return (
-
- :
- }
- onClick={() => setIsSideMenuCollapsed(!isSideMenuCollapsed)}
- style={{ fontSize: "16px", width: 64, height: 64 }}
- />
-
- Header
-
+
useContext(HeaderContext);
+
+export default function HeaderProvider({ children }) {
+ const [totalNotifications, setTotalNotifications] = useState(0);
+ // initially null, then set to [...] on first fetch
+ const [notifications, setNotifications] = useState(null);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/Contexts/WebSocketContext.js b/src/Contexts/WebSocketContext.js
index f5977ab..311c22d 100644
--- a/src/Contexts/WebSocketContext.js
+++ b/src/Contexts/WebSocketContext.js
@@ -8,6 +8,7 @@ import { useNavigate } from "react-router-dom";
import { useUserProfileContext } from "./UserProfileContext";
import { useAdminAreaRolesContext } from "./AdminAreaRolesContext";
import { useUsersContext } from "./UsersContext";
+import { useHeaderContext } from "./HeaderContext";
const WebSocketContext = createContext(null);
@@ -25,6 +26,7 @@ export default function WebSocketProvider({
const wsMessageCache = useRef([]);
const navigate = useNavigate();
const appContext = useAppContext();
+ const headerContext = useHeaderContext();
const sideBarContext = useSideBarContext();
const groupTasksContext = useGroupTasksContext();
const userProfileContext = useUserProfileContext();
@@ -45,6 +47,7 @@ export default function WebSocketProvider({
data.Permissions === null ? [] : data.Permissions
);
appContext.setUsers(data.Users);
+ headerContext.setTotalNotifications(data.TotalNotifications);
sideBarContext.setUsername(data.Username);
sideBarContext.setAvatar(data.Avatar);
sideBarContext.setAvailableCategoryGroups(data.AvailableCategoryGroups);
@@ -67,6 +70,7 @@ export default function WebSocketProvider({
notificationApi,
sideBarContext,
appContext,
+ headerContext,
groupTasksContext,
userProfileContext,
adminAreaRolesContext,
diff --git a/src/Handlers/WebSocketMessageHandler.js b/src/Handlers/WebSocketMessageHandler.js
index 5f23bac..712a540 100644
--- a/src/Handlers/WebSocketMessageHandler.js
+++ b/src/Handlers/WebSocketMessageHandler.js
@@ -42,6 +42,9 @@ export const ReceivedMessagesCommands = {
InstallingGlobalPythonPackagesFinished: 38,
UpdateUsers: 39,
CheckingForGroupTasksCategoryGroupChanges: 40,
+ NewNotification: 41,
+ AllNotificationsDeleted: 42,
+ OneNotificationDeleted: 43,
};
// commands sent to the backend server
@@ -69,6 +72,8 @@ export const SentMessagesCommands = {
GroupTasksInstallPythonPackages: 21,
GroupTasksInstallGlobalPythonPackages: 22,
SubscribeToTopic: 23,
+ DeleteAllNotifications: 24,
+ DeleteOneNotification: 25,
};
/*
@@ -83,6 +88,7 @@ export function handleWebSocketMessage(
notificationApi,
sideBarContext,
appContext,
+ headerContext,
groupTasksContext,
userProfileContext,
adminAreaRolesContext,
@@ -962,6 +968,43 @@ export function handleWebSocketMessage(
description: `This may take a while`,
});
break;
+ case ReceivedMessagesCommands.NewNotification:
+ headerContext.setTotalNotifications(
+ (totalNotifications) => totalNotifications + 1
+ );
+
+ headerContext.setNotifications((arr) => {
+ // only add notifications to the list if the list is not null
+ // this has to do with the get fetch that is executed when the list is empty
+ if (arr === null) return arr;
+
+ const newArr = [...arr];
+
+ newArr.push(body);
+
+ return newArr;
+ });
+ break;
+ case ReceivedMessagesCommands.AllNotificationsDeleted:
+ headerContext.setTotalNotifications(0);
+
+ headerContext.setNotifications([]);
+ break;
+ case ReceivedMessagesCommands.OneNotificationDeleted:
+ headerContext.setTotalNotifications(
+ (totalNotifications) => totalNotifications - 1
+ );
+
+ headerContext.setNotifications((arr) => {
+ if (arr === null) return arr;
+
+ let newArr = [...arr];
+
+ newArr = newArr.filter((notification) => notification.Id !== body);
+
+ return newArr;
+ });
+ break;
default:
console.error("unknown command", cmd);
break;