diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx index 590e4d2..3aa8544 100644 --- a/src/components/layout/TopBar.tsx +++ b/src/components/layout/TopBar.tsx @@ -1,4 +1,5 @@ -import { Search, Bell, ChevronDown, LogOut } from 'lucide-react'; +import { Search, ChevronDown, LogOut } from 'lucide-react'; +import { NotificationPopover } from '@/components/notifications/NotificationPopover'; import { cn } from '@/lib/utils'; import { useAuth } from '@/contexts/AuthContext'; import { @@ -60,17 +61,7 @@ export function TopBar({ title, description }: TopBarProps) { {/* Notifications */} - - - - 3 - - + {/* Profile Dropdown */} diff --git a/src/components/notifications/NotificationPopover.tsx b/src/components/notifications/NotificationPopover.tsx new file mode 100644 index 0000000..95f31b8 --- /dev/null +++ b/src/components/notifications/NotificationPopover.tsx @@ -0,0 +1,273 @@ + +import { useState } from 'react'; +import { + Bell, + Check, + CheckCircle2, + AlertTriangle, + Banknote, + Info, + X, + CreditCard +} from 'lucide-react'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from '@/components/ui/tabs'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { cn } from '@/lib/utils'; + +// --- Types --- +export type NotificationType = 'critical' | 'info' | 'success' | 'warning'; +export type NotificationCategory = 'finance' | 'security' | 'system'; + +export interface Notification { + id: string; + type: NotificationType; + category: NotificationCategory; + title: string; + message: string; + time: string; + actionLabel?: string; + isRead: boolean; +} + +// --- Mock Data --- +const initialNotifications: Notification[] = [ + { + id: '1', + type: 'critical', + category: 'security', + title: 'Suspicious Login Detected', + message: 'New login from IP 192.168.1.1 (Hamburg, DE).', + time: '2m ago', + actionLabel: 'Block', + isRead: false, + }, + { + id: '2', + type: 'info', + category: 'finance', + title: 'Payment Received', + message: 'Partner "Neon Arena" settled invoice #KB-902.', + time: '1h ago', + actionLabel: 'View', + isRead: false, + }, + { + id: '3', + type: 'warning', + category: 'system', + title: 'High Server Load', + message: 'CPU usage is continuously above 85%.', + time: '3h ago', + isRead: true, + }, + { + id: '4', + type: 'success', + category: 'finance', + title: 'Payout Processed', + message: 'Monthly payouts for 12 partners completed.', + time: '5h ago', + isRead: true, + }, + { + id: '5', + type: 'info', + category: 'system', + title: 'System Update', + message: 'Eventify Core v2.4.0 is now live.', + time: '1d ago', + isRead: true, + }, +]; + +export function NotificationPopover() { + const [notifications, setNotifications] = useState(initialNotifications); + const [isOpen, setIsOpen] = useState(false); + + const unreadCount = notifications.filter(n => !n.isRead && n.type === 'critical').length; + const allUnreadCount = notifications.filter(n => !n.isRead).length; + + const markAsRead = (id: string) => { + setNotifications(prev => prev.map(n => n.id === id ? { ...n, isRead: true } : n)); + }; + + const markAllAsRead = () => { + setNotifications(prev => prev.map(n => ({ ...n, isRead: true }))); + }; + + const deleteNotification = (id: string, e: React.MouseEvent) => { + e.stopPropagation(); + setNotifications(prev => prev.filter(n => n.id !== id)); + }; + + const getIcon = (type: NotificationType, category: NotificationCategory) => { + if (category === 'finance') return ; + if (type === 'critical') return ; + if (type === 'success') return ; + return ; + }; + + const getStyles = (n: Notification) => { + if (n.type === 'critical') return "bg-red-500/10 border-l-4 border-red-500 hover:bg-red-500/15"; + if (n.category === 'finance') return "bg-blue-500/10 border-l-4 border-blue-500 hover:bg-blue-500/15"; + if (n.type === 'success') return "bg-green-500/10 border-l-4 border-green-500 hover:bg-green-500/15"; + return "bg-secondary/30 border-l-4 border-secondary hover:bg-secondary/50"; + }; + + const NotificationList = ({ items }: { items: Notification[] }) => { + if (items.length === 0) { + return ( + + + + + All caught up! + No new alerts to review at this time. + + ); + } + + return ( + + {items.map((n) => ( + markAsRead(n.id)} + > + + + {getIcon(n.type, n.category)} + + + + + {n.title} + + {n.time} + + + {n.message} + + {n.actionLabel && ( + { + e.stopPropagation(); + markAsRead(n.id); + }} + > + {n.actionLabel} + + )} + + deleteNotification(n.id, e)} + > + + + + {!n.isRead && ( + + )} + + ))} + + ); + }; + + return ( + + + + + {unreadCount > 0 && ( + + + + )} + + + + + + Notifications + {allUnreadCount > 0 && ( + + Mark all as read + + )} + + + + All + + + Alerts + {unreadCount > 0 && ( + + {unreadCount} + + )} + + + Finance + + + + + + + + + n.type === 'critical')} /> + + + n.category === 'finance')} /> + + + + + + ); +}
No new alerts to review at this time.
+ {n.title} +
+ {n.message} +