Kraft102's picture
fix: sql.js Docker/Alpine compatibility layer for PatternMemory and FailureMemory
5a81b95
import { useState, useEffect } from 'react';
import {
Bell, BellOff, Check, CheckCheck, Trash2, Volume2, VolumeX,
Monitor, AlertTriangle, AlertCircle, Info, Settings, ShieldCheck, ShieldX
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Switch } from '@/components/ui/switch';
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 { useNotifications, NotificationSeverity, requestNotificationPermission, getNotificationPermission } from '@/contexts/NotificationContext';
import { cn } from '@/lib/utils';
const severityConfig: Record<NotificationSeverity, { icon: any; color: string }> = {
critical: { icon: AlertTriangle, color: 'text-destructive' },
warning: { icon: AlertCircle, color: 'text-orange-400' },
info: { icon: Info, color: 'text-primary' },
};
const NotificationPermissionStatus = () => {
const [permission, setPermission] = useState<NotificationPermission | 'unsupported'>('default');
useEffect(() => {
setPermission(getNotificationPermission());
}, []);
if (permission === 'unsupported') {
return (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<ShieldX className="w-3 h-3" />
<span>Browser understøtter ikke notifikationer</span>
</div>
);
}
if (permission === 'denied') {
return (
<div className="flex items-center gap-2 text-xs text-destructive">
<ShieldX className="w-3 h-3" />
<span>Notifikationer blokeret - tillad i browser-indstillinger</span>
</div>
);
}
if (permission === 'granted') {
return (
<div className="flex items-center gap-2 text-xs text-green-500">
<ShieldCheck className="w-3 h-3" />
<span>Notifikationer aktiveret</span>
</div>
);
}
return null;
};
const NotificationCenter = () => {
const {
notifications,
unreadCount,
settings,
markAsRead,
markAllAsRead,
clearAll,
updateSettings,
addNotification
} = useNotifications();
const [activeTab, setActiveTab] = useState('notifications');
const sendTestNotification = (severity: NotificationSeverity) => {
const testAlerts: Record<NotificationSeverity, { title: string; message: string }> = {
critical: {
title: 'KRITISK: Ransomware Detekteret',
message: 'Mistænkelig krypteringsaktivitet opdaget på endpoint WKS-2847. Øjeblikkelig handling påkrævet.',
},
warning: {
title: 'Advarsel: Brute Force Forsøg',
message: 'Flere mislykkede login-forsøg fra IP 192.168.1.105 på admin-konto.',
},
info: {
title: 'System Opdatering',
message: 'Sikkerhedspatch KB5034441 er tilgængelig til installation.',
},
};
const alert = testAlerts[severity];
addNotification({
title: alert.title,
message: alert.message,
severity,
source: 'Test',
});
};
const formatTime = (timestamp: number) => {
const diff = Date.now() - timestamp;
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (minutes < 1) return 'Lige nu';
if (minutes < 60) return `${minutes}m siden`;
if (hours < 24) return `${hours}t siden`;
return `${days}d siden`;
};
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="ghost" size="sm" className="relative">
<Bell className="w-5 h-5" />
{unreadCount > 0 && (
<span className="absolute -top-1 -right-1 w-5 h-5 bg-destructive text-destructive-foreground text-xs rounded-full flex items-center justify-center animate-pulse">
{unreadCount > 9 ? '9+' : unreadCount}
</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="w-80 p-0 bg-card border-border">
<Tabs value={activeTab} onValueChange={setActiveTab}>
<div className="flex items-center justify-between px-4 py-2 border-b border-border">
<TabsList className="grid grid-cols-2 h-8 bg-secondary/30">
<TabsTrigger value="notifications" className="text-xs">
Notifikationer
</TabsTrigger>
<TabsTrigger value="settings" className="text-xs">
Indstillinger
</TabsTrigger>
</TabsList>
</div>
<TabsContent value="notifications" className="m-0">
{notifications.length > 0 && (
<div className="flex items-center justify-between px-4 py-2 border-b border-border/50">
<Button variant="ghost" size="sm" onClick={markAllAsRead} className="h-7 text-xs">
<CheckCheck className="w-3 h-3 mr-1" />
Marker alle læst
</Button>
<Button variant="ghost" size="sm" onClick={clearAll} className="h-7 text-xs text-destructive hover:text-destructive">
<Trash2 className="w-3 h-3 mr-1" />
Ryd
</Button>
</div>
)}
<ScrollArea className="h-[300px]">
{notifications.length === 0 ? (
<div className="flex flex-col items-center justify-center h-[200px] text-muted-foreground">
<BellOff className="w-10 h-10 mb-2 opacity-50" />
<p className="text-sm">Ingen notifikationer</p>
</div>
) : (
<div className="divide-y divide-border/50">
{notifications.map((notification) => {
const config = severityConfig[notification.severity];
const Icon = config.icon;
return (
<div
key={notification.id}
className={cn(
"px-4 py-3 hover:bg-secondary/30 transition-colors cursor-pointer",
!notification.read && "bg-primary/5"
)}
onClick={() => markAsRead(notification.id)}
>
<div className="flex items-start gap-3">
<Icon className={cn("w-4 h-4 mt-0.5 shrink-0", config.color)} />
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2">
<p className="text-sm font-medium truncate">{notification.title}</p>
{!notification.read && (
<span className="w-2 h-2 rounded-full bg-primary shrink-0" />
)}
</div>
<p className="text-xs text-muted-foreground line-clamp-2 mt-0.5">
{notification.message}
</p>
<p className="text-xs text-muted-foreground/60 mt-1">
{formatTime(notification.timestamp)}
</p>
</div>
</div>
</div>
);
})}
</div>
)}
</ScrollArea>
</TabsContent>
<TabsContent value="settings" className="m-0 p-4 space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
{settings.soundEnabled ? <Volume2 className="w-4 h-4" /> : <VolumeX className="w-4 h-4" />}
<span className="text-sm">Lyd</span>
</div>
<Switch
checked={settings.soundEnabled}
onCheckedChange={(v) => updateSettings({ soundEnabled: v })}
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Monitor className="w-4 h-4" />
<span className="text-sm">Desktop notifikationer</span>
</div>
<Switch
checked={settings.desktopEnabled}
onCheckedChange={async (v) => {
if (v) {
const permission = await requestNotificationPermission();
if (permission === 'granted') {
updateSettings({ desktopEnabled: true });
}
} else {
updateSettings({ desktopEnabled: false });
}
}}
/>
</div>
<NotificationPermissionStatus />
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<AlertTriangle className="w-4 h-4" />
<span className="text-sm">Kun kritiske</span>
</div>
<Switch
checked={settings.criticalOnly}
onCheckedChange={(v) => updateSettings({ criticalOnly: v })}
/>
</div>
<div className="pt-3 border-t border-border/50 space-y-2">
<p className="text-xs text-muted-foreground mb-2">Test notifikationer</p>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
className="flex-1 text-xs h-8 border-destructive/50 text-destructive hover:bg-destructive hover:text-destructive-foreground"
onClick={() => sendTestNotification('critical')}
>
Kritisk
</Button>
<Button
variant="outline"
size="sm"
className="flex-1 text-xs h-8 border-orange-400/50 text-orange-400 hover:bg-orange-400 hover:text-white"
onClick={() => sendTestNotification('warning')}
>
Advarsel
</Button>
<Button
variant="outline"
size="sm"
className="flex-1 text-xs h-8 border-primary/50 text-primary hover:bg-primary hover:text-primary-foreground"
onClick={() => sendTestNotification('info')}
>
Info
</Button>
</div>
</div>
</TabsContent>
</Tabs>
</PopoverContent>
</Popover>
);
};
export default NotificationCenter;