'use client' import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react' // Types export type AlertImportance = 'KRITISCH' | 'DRINGEND' | 'WICHTIG' | 'PRUEFEN' | 'INFO' export interface AlertSource { title: string url: string domain: string } export interface Alert { id: string title: string summary: string // LLM-generierte Zusammenfassung source: string importance: AlertImportance timestamp: Date isRead: boolean sources: AlertSource[] topicId?: string } export interface Topic { id: string name: string keywords: string[] googleAlertUrl?: string rssFeedUrl?: string isActive: boolean icon?: string } export interface AlertsSettings { notificationFrequency: 'realtime' | 'hourly' | 'daily' minImportance: AlertImportance wizardCompleted: boolean } interface AlertsContextType { alerts: Alert[] unreadCount: number isLoading: boolean error: string | null fetchAlerts: () => Promise markAsRead: (id: string) => void markAllAsRead: () => void topics: Topic[] addTopic: (topic: Topic) => void updateTopic: (id: string, updates: Partial) => void removeTopic: (id: string) => void settings: AlertsSettings updateSettings: (settings: Partial) => void } const AlertsContext = createContext(null) // LocalStorage Keys const ALERTS_KEY = 'bp_alerts' const TOPICS_KEY = 'bp_alerts_topics' const SETTINGS_KEY = 'bp_alerts_settings' // Default settings const defaultSettings: AlertsSettings = { notificationFrequency: 'daily', minImportance: 'INFO', wizardCompleted: false } // Vordefinierte Themen fuer Lehrer export const lehrerThemen: Omit[] = [ { name: 'Bildungspolitik', icon: '📜', keywords: ['Kultusministerium', 'Schulreform', 'Lehrplan', 'Bildungsminister'], isActive: false }, { name: 'Digitale Bildung', icon: '💻', keywords: ['iPad Schule', 'digitale Medien', 'E-Learning', 'Tablet Unterricht'], isActive: false }, { name: 'Inklusion', icon: '🤝', keywords: ['Förderschule', 'Integration', 'barrierefreies Lernen', 'Inklusion Schule'], isActive: false }, { name: 'Abitur & Prüfungen', icon: '📝', keywords: ['Abitur', 'Zentralabitur', 'Prüfungsordnung', 'Abiturprüfung'], isActive: false }, { name: 'Lehrerberuf', icon: '👩‍🏫', keywords: ['Lehrkräftemangel', 'Besoldung', 'Quereinsteiger', 'Lehrergehalt'], isActive: false }, { name: 'KI in der Schule', icon: '🤖', keywords: ['ChatGPT Schule', 'KI Unterricht', 'künstliche Intelligenz Bildung'], isActive: false }, ] // Mock-Alerts für Demo (werden später durch Backend ersetzt) const mockAlerts: Alert[] = [ { id: 'alert-1', title: 'Niedersachsen plant Digitalisierungsoffensive an Schulen', summary: 'Das Kultusministerium Niedersachsen kündigt ein 50-Millionen-Programm zur Digitalisierung an. Alle weiterführenden Schulen sollen bis 2027 mit interaktiven Displays ausgestattet werden. Fortbildungen für Lehrkräfte sind Teil des Pakets.', source: 'Hannoversche Allgemeine', importance: 'WICHTIG', timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000), isRead: false, sources: [ { title: 'Digitalisierung in Niedersachsens Schulen', url: 'https://example.com/1', domain: 'haz.de' } ], topicId: 'digitale-bildung' }, { id: 'alert-2', title: 'Neue Richtlinien für ChatGPT im Unterricht veröffentlicht', summary: 'Die KMK hat Empfehlungen zum Umgang mit KI-Tools im Unterricht herausgegeben. Lehrkräfte sollen KI als Werkzeug nutzen dürfen, Prüfungsleistungen müssen jedoch eigenständig erbracht werden.', source: 'Der Spiegel', importance: 'DRINGEND', timestamp: new Date(Date.now() - 5 * 60 * 60 * 1000), isRead: false, sources: [ { title: 'KMK-Richtlinien zu KI', url: 'https://example.com/2', domain: 'spiegel.de' } ], topicId: 'ki-schule' }, { id: 'alert-3', title: 'Lehrerverband fordert bessere Besoldung', summary: 'Der Deutsche Lehrerverband kritisiert die aktuelle Besoldungssituation. Besonders Grundschullehrkräfte seien im Vergleich zu anderen Bundesländern benachteiligt.', source: 'Zeit Online', importance: 'PRUEFEN', timestamp: new Date(Date.now() - 24 * 60 * 60 * 1000), isRead: true, sources: [ { title: 'Lehrergehälter im Vergleich', url: 'https://example.com/3', domain: 'zeit.de' } ], topicId: 'lehrerberuf' } ] export function AlertsProvider({ children }: { children: ReactNode }) { const [alerts, setAlerts] = useState([]) const [topics, setTopics] = useState([]) const [settings, setSettings] = useState(defaultSettings) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) const [mounted, setMounted] = useState(false) // Nach dem ersten Render: Daten aus localStorage laden useEffect(() => { const storedAlerts = localStorage.getItem(ALERTS_KEY) const storedTopics = localStorage.getItem(TOPICS_KEY) const storedSettings = localStorage.getItem(SETTINGS_KEY) if (storedAlerts) { try { const parsed = JSON.parse(storedAlerts) // Konvertiere timestamp strings zu Date objects setAlerts(parsed.map((a: any) => ({ ...a, timestamp: new Date(a.timestamp) }))) } catch (e) { console.error('Error parsing stored alerts:', e) setAlerts(mockAlerts) } } else { // Demo-Alerts laden setAlerts(mockAlerts) } if (storedTopics) { try { setTopics(JSON.parse(storedTopics)) } catch (e) { console.error('Error parsing stored topics:', e) } } if (storedSettings) { try { setSettings({ ...defaultSettings, ...JSON.parse(storedSettings) }) } catch (e) { console.error('Error parsing stored settings:', e) } } setMounted(true) }, []) // Alerts in localStorage speichern useEffect(() => { if (mounted && alerts.length > 0) { localStorage.setItem(ALERTS_KEY, JSON.stringify(alerts)) } }, [alerts, mounted]) // Topics in localStorage speichern useEffect(() => { if (mounted) { localStorage.setItem(TOPICS_KEY, JSON.stringify(topics)) } }, [topics, mounted]) // Settings in localStorage speichern useEffect(() => { if (mounted) { localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings)) } }, [settings, mounted]) // Alerts vom Backend abrufen const fetchAlerts = useCallback(async () => { setIsLoading(true) setError(null) try { // TODO: Backend-Integration // const response = await fetch('http://macmini:8000/api/alerts/') // const data = await response.json() // setAlerts(data) // Für jetzt: Mock-Daten verwenden await new Promise(resolve => setTimeout(resolve, 500)) // Alerts sind bereits geladen } catch (err) { setError('Fehler beim Laden der Alerts') console.error('Error fetching alerts:', err) } finally { setIsLoading(false) } }, []) // Alert als gelesen markieren const markAsRead = useCallback((id: string) => { setAlerts(prev => prev.map(alert => alert.id === id ? { ...alert, isRead: true } : alert )) }, []) // Alle Alerts als gelesen markieren const markAllAsRead = useCallback(() => { setAlerts(prev => prev.map(alert => ({ ...alert, isRead: true }))) }, []) // Topic hinzufügen const addTopic = useCallback((topic: Topic) => { setTopics(prev => [...prev, topic]) }, []) // Topic aktualisieren const updateTopic = useCallback((id: string, updates: Partial) => { setTopics(prev => prev.map(topic => topic.id === id ? { ...topic, ...updates } : topic )) }, []) // Topic entfernen const removeTopic = useCallback((id: string) => { setTopics(prev => prev.filter(topic => topic.id !== id)) }, []) // Settings aktualisieren const updateSettings = useCallback((updates: Partial) => { setSettings(prev => ({ ...prev, ...updates })) }, []) // Ungelesene Alerts zählen const unreadCount = alerts.filter(a => !a.isRead).length // Während SSR: Default-Werte anzeigen if (!mounted) { return ( {}, markAsRead: () => {}, markAllAsRead: () => {}, topics: [], addTopic: () => {}, updateTopic: () => {}, removeTopic: () => {}, settings: defaultSettings, updateSettings: () => {}, }} > {children} ) } return ( {children} ) } // Hook fuer einfache Verwendung export function useAlerts() { const context = useContext(AlertsContext) if (!context) { throw new Error('useAlerts must be used within an AlertsProvider') } return context } // Hilfsfunktion: Importance-Farben export function getImportanceColor(importance: AlertImportance, isDark: boolean): string { const colors = { 'KRITISCH': isDark ? 'bg-red-500/20 text-red-300 border-red-500/30' : 'bg-red-100 text-red-700 border-red-200', 'DRINGEND': isDark ? 'bg-orange-500/20 text-orange-300 border-orange-500/30' : 'bg-orange-100 text-orange-700 border-orange-200', 'WICHTIG': isDark ? 'bg-yellow-500/20 text-yellow-300 border-yellow-500/30' : 'bg-yellow-100 text-yellow-700 border-yellow-200', 'PRUEFEN': isDark ? 'bg-blue-500/20 text-blue-300 border-blue-500/30' : 'bg-blue-100 text-blue-700 border-blue-200', 'INFO': isDark ? 'bg-slate-500/20 text-slate-300 border-slate-500/30' : 'bg-slate-100 text-slate-600 border-slate-200', } return colors[importance] } // Hilfsfunktion: Relative Zeitangabe export function getRelativeTime(date: Date): string { const now = new Date() const diffMs = now.getTime() - date.getTime() const diffMins = Math.floor(diffMs / (1000 * 60)) const diffHours = Math.floor(diffMs / (1000 * 60 * 60)) const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)) if (diffMins < 1) return 'Gerade eben' if (diffMins < 60) return `vor ${diffMins} Min.` if (diffHours < 24) return `vor ${diffHours} Std.` if (diffDays === 1) return 'Gestern' if (diffDays < 7) return `vor ${diffDays} Tagen` return date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' }) }