79 lines
3.0 KiB
TypeScript
79 lines
3.0 KiB
TypeScript
|
|
import React, { useState, useEffect, useRef } from 'react';
|
||
|
|
import type { NotificationPanelProps } from '../types/ui.types';
|
||
|
|
|
||
|
|
interface Notification {
|
||
|
|
id: string;
|
||
|
|
icon: string;
|
||
|
|
title: string;
|
||
|
|
timestamp: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
const initialNotifications: Notification[] = [
|
||
|
|
{ id: 'n1', icon: '💾', title: 'Projekt automatisch gespeichert', timestamp: 'vor 2 Min.' },
|
||
|
|
{ id: 'n2', icon: '🔄', title: 'Neue Version verfügbar', timestamp: 'vor 15 Min.' },
|
||
|
|
{ id: 'n3', icon: '👤', title: 'Benutzer ist beigetreten', timestamp: 'vor 1 Std.' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const NotificationPanel: React.FC<NotificationPanelProps> = ({ open, onClose }) => {
|
||
|
|
const [notifications, setNotifications] = useState<Notification[]>(initialNotifications);
|
||
|
|
const [allRead, setAllRead] = useState(false);
|
||
|
|
const ref = useRef<HTMLDivElement>(null);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!open) return;
|
||
|
|
const handleClickOutside = (e: MouseEvent) => {
|
||
|
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||
|
|
onClose();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
document.addEventListener('mousedown', handleClickOutside);
|
||
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||
|
|
}, [open, onClose]);
|
||
|
|
|
||
|
|
if (!open) return null;
|
||
|
|
|
||
|
|
const handleDismiss = (id: string) => {
|
||
|
|
setNotifications((prev) => prev.filter((n) => n.id !== id));
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleMarkAllRead = () => {
|
||
|
|
setAllRead(true);
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="notif-panel" ref={ref}>
|
||
|
|
<div className="notif-header">
|
||
|
|
<h3>Benachrichtigungen</h3>
|
||
|
|
<button className="notif-close" aria-label="Schließen" onClick={onClose}>
|
||
|
|
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<div className="notif-list">
|
||
|
|
{notifications.length === 0 ? (
|
||
|
|
<p className="notif-empty">Keine Benachrichtigungen</p>
|
||
|
|
) : (
|
||
|
|
notifications.map((n) => (
|
||
|
|
<div key={n.id} className={`notif-item ${allRead ? 'read' : ''}`}>
|
||
|
|
<span className="notif-icon">{n.icon}</span>
|
||
|
|
<div className="notif-content">
|
||
|
|
<span className="notif-title">{n.title}</span>
|
||
|
|
<span className="notif-time">{n.timestamp}</span>
|
||
|
|
</div>
|
||
|
|
<button className="notif-dismiss" aria-label="Entfernen" onClick={() => handleDismiss(n.id)}>
|
||
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
))
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
{notifications.length > 0 && (
|
||
|
|
<button className="notif-mark-all" onClick={handleMarkAllRead}>
|
||
|
|
Alle als gelesen markieren
|
||
|
|
</button>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default NotificationPanel;
|