'use client' import { useEffect, useState, useCallback } from 'react' import type { AlertItem, Topic, Rule, Profile, Stats } from './types' const API_BASE = '/api/alerts' export function useAlertsData() { const [stats, setStats] = useState(null) const [alerts, setAlerts] = useState([]) const [topics, setTopics] = useState([]) const [rules, setRules] = useState([]) const [profile, setProfile] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [inboxFilter, setInboxFilter] = useState('all') const fetchData = useCallback(async () => { try { const [statsRes, alertsRes, topicsRes, rulesRes, profileRes] = await Promise.all([ fetch(`${API_BASE}/stats`), fetch(`${API_BASE}/inbox?limit=50`), fetch(`${API_BASE}/topics`), fetch(`${API_BASE}/rules`), fetch(`${API_BASE}/profile`), ]) if (statsRes.ok) setStats(await statsRes.json()) if (alertsRes.ok) { const data = await alertsRes.json() setAlerts(data.items || []) } if (topicsRes.ok) { const data = await topicsRes.json() setTopics(data.topics || data.items || []) } if (rulesRes.ok) { const data = await rulesRes.json() setRules(data.rules || data.items || []) } if (profileRes.ok) setProfile(await profileRes.json()) setError(null) } catch (err) { setError(err instanceof Error ? err.message : 'Verbindungsfehler') setStats({ total_alerts: 147, new_alerts: 23, kept_alerts: 89, review_alerts: 12, dropped_alerts: 23, total_topics: 5, active_topics: 4, total_rules: 8, }) setAlerts([ { id: 'demo_1', title: 'Neue Studie zur digitalen Bildung an Schulen', url: 'https://example.com/artikel1', snippet: 'Eine aktuelle Studie zeigt, dass digitale Lernmittel den Lernerfolg steigern koennen...', topic_name: 'Digitale Bildung', relevance_score: 0.85, relevance_decision: 'KEEP', status: 'new', fetched_at: new Date().toISOString(), published_at: null, matched_rule: null, tags: ['bildung', 'digital'], }, { id: 'demo_2', title: 'Inklusion: Fortbildungen fuer Lehrkraefte', url: 'https://example.com/artikel2', snippet: 'Das Kultusministerium bietet neue Fortbildungsangebote zum Thema Inklusion an...', topic_name: 'Inklusion', relevance_score: 0.72, relevance_decision: 'KEEP', status: 'new', fetched_at: new Date(Date.now() - 3600000).toISOString(), published_at: null, matched_rule: null, tags: ['inklusion'], }, ]) setTopics([ { id: 'topic_1', name: 'Digitale Bildung', feed_url: 'https://google.com/alerts/feeds/123', feed_type: 'rss', is_active: true, fetch_interval_minutes: 60, last_fetched_at: new Date().toISOString(), alert_count: 47, }, { id: 'topic_2', name: 'Inklusion', feed_url: 'https://google.com/alerts/feeds/456', feed_type: 'rss', is_active: true, fetch_interval_minutes: 60, last_fetched_at: new Date(Date.now() - 1800000).toISOString(), alert_count: 32, }, ]) setRules([ { id: 'rule_1', name: 'Stellenanzeigen ausschliessen', topic_id: null, conditions: [{ field: 'title', operator: 'contains', value: 'Stellenangebot' }], action_type: 'drop', action_config: {}, priority: 10, is_active: true, }, ]) setProfile({ priorities: ['Inklusion', 'digitale Bildung'], exclusions: ['Stellenanzeigen', 'Werbung'], positive_examples: [], negative_examples: [], policies: { keep_threshold: 0.7, drop_threshold: 0.3 }, }) } finally { setLoading(false) } }, []) useEffect(() => { fetchData() }, [fetchData]) const filteredAlerts = alerts.filter((alert) => { if (inboxFilter === 'all') return true if (inboxFilter === 'new') return alert.status === 'new' if (inboxFilter === 'keep') return alert.relevance_decision === 'KEEP' if (inboxFilter === 'review') return alert.relevance_decision === 'REVIEW' return true }) return { stats, alerts, topics, rules, profile, loading, error, inboxFilter, setInboxFilter, filteredAlerts, } } // --- Helper functions (pure, no hooks) --- export function formatTimeAgo(dateStr: string | null): string { if (!dateStr) return '-' const date = new Date(dateStr) const now = new Date() const diffMs = now.getTime() - date.getTime() const diffMins = Math.floor(diffMs / 60000) if (diffMins < 1) return 'gerade eben' if (diffMins < 60) return `vor ${diffMins} Min.` if (diffMins < 1440) return `vor ${Math.floor(diffMins / 60)} Std.` return `vor ${Math.floor(diffMins / 1440)} Tagen` } export function getScoreBadgeClass(score: number | null): { pct: number; cls: string } | null { if (score === null) return null const pct = Math.round(score * 100) let cls = 'bg-slate-100 text-slate-600' if (pct >= 70) cls = 'bg-green-100 text-green-800' else if (pct >= 40) cls = 'bg-amber-100 text-amber-800' else cls = 'bg-red-100 text-red-800' return { pct, cls } } export function getDecisionBadgeClass(decision: string | null): { decision: string; cls: string } | null { if (!decision) return null const styles: Record = { KEEP: 'bg-green-100 text-green-800', REVIEW: 'bg-amber-100 text-amber-800', DROP: 'bg-red-100 text-red-800', } return { decision, cls: styles[decision] || 'bg-slate-100' } }