+
{user.email}
diff --git a/ApiCore-Frontend/src/components/notification-bell.tsx b/ApiCore-Frontend/src/components/notification-bell.tsx
new file mode 100644
index 0000000..61fe39f
--- /dev/null
+++ b/ApiCore-Frontend/src/components/notification-bell.tsx
@@ -0,0 +1,100 @@
+"use client";
+
+import Link from "next/link";
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+import { Bell } from "lucide-react";
+import { fetchNotifications, markNotificationRead } from "@/lib/notifications/api";
+import { Notification, NotificationPriority } from "@/lib/notifications/types";
+import { Badge, badgeVariants } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+const PRIORITY_VARIANT: Record
[0]>["variant"]> = {
+ INFO: "secondary",
+ HINWEIS: "outline",
+ WARNUNG: "warning",
+ KRITISCH: "destructive",
+};
+
+function notificationHref(notification: Notification): string | null {
+ if (notification.relatedType === "Colony") return `/colonies/${notification.relatedId}`;
+ if (notification.relatedType === "Apiary") return `/apiaries/${notification.relatedId}`;
+ if (notification.relatedType === "Task") return "/tasks";
+ return null;
+}
+
+export function NotificationBell() {
+ const queryClient = useQueryClient();
+ const { data: notifications } = useQuery({
+ queryKey: ["notifications"],
+ queryFn: () => fetchNotifications(),
+ refetchInterval: 60_000,
+ });
+
+ const readMutation = useMutation({
+ mutationFn: markNotificationRead,
+ onSuccess: () => {
+ void queryClient.invalidateQueries({ queryKey: ["notifications"] });
+ },
+ });
+
+ const unreadCount = notifications?.filter((notification) => !notification.read).length ?? 0;
+
+ return (
+
+
+ }
+ >
+
+ {unreadCount > 0 ? (
+
+ {unreadCount > 9 ? "9+" : unreadCount}
+
+ ) : null}
+
+
+ {!notifications || notifications.length === 0 ? (
+ Keine Benachrichtigungen.
+ ) : (
+ notifications.slice(0, 10).map((notification) => {
+ const href = notificationHref(notification);
+ const item = (
+
+
+
+ {notification.priority}
+
+
+ {new Date(notification.createdAt).toLocaleDateString("de-DE")}
+
+
+
{notification.message}
+
+ );
+ return (
+ {
+ if (!notification.read) readMutation.mutate(notification.id);
+ }}
+ render={href ? : undefined}
+ >
+ {item}
+
+ );
+ })
+ )}
+
+
+ );
+}
diff --git a/ApiCore-Frontend/src/lib/notifications/api.ts b/ApiCore-Frontend/src/lib/notifications/api.ts
new file mode 100644
index 0000000..6483175
--- /dev/null
+++ b/ApiCore-Frontend/src/lib/notifications/api.ts
@@ -0,0 +1,10 @@
+import { apiFetch } from "@/lib/auth/api-client";
+import { Notification } from "./types";
+
+export function fetchNotifications(unreadOnly = false): Promise {
+ return apiFetch(`/notifications?unreadOnly=${unreadOnly}`);
+}
+
+export function markNotificationRead(id: string): Promise {
+ return apiFetch(`/notifications/${id}/read`, { method: "POST" });
+}
diff --git a/ApiCore-Frontend/src/lib/notifications/types.ts b/ApiCore-Frontend/src/lib/notifications/types.ts
new file mode 100644
index 0000000..ccd7a6c
--- /dev/null
+++ b/ApiCore-Frontend/src/lib/notifications/types.ts
@@ -0,0 +1,11 @@
+export type NotificationPriority = "INFO" | "HINWEIS" | "WARNUNG" | "KRITISCH";
+
+export interface Notification {
+ id: string;
+ priority: NotificationPriority;
+ message: string;
+ relatedType: string | null;
+ relatedId: string | null;
+ read: boolean;
+ createdAt: string;
+}
diff --git a/TASKS.md b/TASKS.md
index 0bb2d0e..4128de4 100644
--- a/TASKS.md
+++ b/TASKS.md
@@ -212,7 +212,7 @@ Reihenfolge = Bau-Reihenfolge. Gleiche Haken-Regel wie MVP: erst setzen, wenn Li
- Endpunkte: `GET /api/v1/notifications`, `POST /api/v1/notifications/:id/read`
- Frontend: Glocken-Icon in der Navigation mit Liste/Badge für ungelesene Einträge
- [x] Backend-Modul fertig, getestet, grün, committet
-- [ ] Frontend-Views fertig, getestet, grün, committet
+- [x] Frontend-Views fertig, getestet, grün, committet
### 27. Statistik und Reporting