From c8cc8774db33382f41c695ae492cef7a94348dd4 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 5 Mar 2026 17:36:22 +0100 Subject: [PATCH] refactor: Video Chat, Voice Service, Alerts Seiten aus Core Admin entfernt - Kommunikation-Seiten nach Lehrer migriert - API-Routes, Health-Check, Navigation bereinigt - Screen-Flow, SBOM, Tests aktualisiert Co-Authored-By: Claude Opus 4.6 --- .../app/(admin)/communication/alerts/page.tsx | 912 ------------------ .../app/(admin)/communication/mail/page.tsx | 1 - .../app/(admin)/communication/matrix/page.tsx | 594 ------------ .../(admin)/communication/video-chat/page.tsx | 635 ------------ .../(admin)/development/screen-flow/page.tsx | 11 +- .../app/(admin)/infrastructure/gpu/page.tsx | 1 - .../app/(admin)/infrastructure/sbom/page.tsx | 5 - .../app/(admin)/infrastructure/tests/page.tsx | 3 - .../api/admin/communication/stats/route.ts | 210 ---- admin-core/app/api/admin/health/route.ts | 2 - admin-core/app/api/alerts/[...path]/route.ts | 172 ---- admin-core/lib/navigation.ts | 26 +- 12 files changed, 3 insertions(+), 2569 deletions(-) delete mode 100644 admin-core/app/(admin)/communication/alerts/page.tsx delete mode 100644 admin-core/app/(admin)/communication/matrix/page.tsx delete mode 100644 admin-core/app/(admin)/communication/video-chat/page.tsx delete mode 100644 admin-core/app/api/admin/communication/stats/route.ts delete mode 100644 admin-core/app/api/alerts/[...path]/route.ts diff --git a/admin-core/app/(admin)/communication/alerts/page.tsx b/admin-core/app/(admin)/communication/alerts/page.tsx deleted file mode 100644 index 3d73dd3..0000000 --- a/admin-core/app/(admin)/communication/alerts/page.tsx +++ /dev/null @@ -1,912 +0,0 @@ -'use client' - -/** - * Alerts Monitoring Admin Page (migrated from website/admin/alerts) - * - * Google Alerts & Feed-Ueberwachung Dashboard - * Provides inbox management, topic configuration, rule builder, and relevance profiles - */ - -import { useEffect, useState, useCallback } from 'react' -import { PagePurpose } from '@/components/common/PagePurpose' - -// Types -interface AlertItem { - id: string - title: string - url: string - snippet: string - topic_name: string - relevance_score: number | null - relevance_decision: string | null - status: string - fetched_at: string - published_at: string | null - matched_rule: string | null - tags: string[] -} - -interface Topic { - id: string - name: string - feed_url: string - feed_type: string - is_active: boolean - fetch_interval_minutes: number - last_fetched_at: string | null - alert_count: number -} - -interface Rule { - id: string - name: string - topic_id: string | null - conditions: Array<{ - field: string - operator: string - value: string | number - }> - action_type: string - action_config: Record - priority: number - is_active: boolean -} - -interface Profile { - priorities: string[] - exclusions: string[] - positive_examples: Array<{ title: string; url: string }> - negative_examples: Array<{ title: string; url: string }> - policies: { - keep_threshold: number - drop_threshold: number - } -} - -interface Stats { - total_alerts: number - new_alerts: number - kept_alerts: number - review_alerts: number - dropped_alerts: number - total_topics: number - active_topics: number - total_rules: number -} - -// Tab type -type TabId = 'dashboard' | 'inbox' | 'topics' | 'rules' | 'profile' | 'audit' | 'documentation' - -export default function AlertsPage() { - const [activeTab, setActiveTab] = useState('dashboard') - 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 API_BASE = '/api/alerts' - - 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') - // Set demo data - 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 formatTimeAgo = (dateStr: string | null) => { - 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` - } - - const getScoreBadge = (score: number | 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}% - } - - const getDecisionBadge = (decision: 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} - - ) - } - - 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 - }) - - const tabs: { id: TabId; label: string; badge?: number }[] = [ - { id: 'dashboard', label: 'Dashboard' }, - { id: 'inbox', label: 'Inbox', badge: stats?.new_alerts || 0 }, - { id: 'topics', label: 'Topics' }, - { id: 'rules', label: 'Regeln' }, - { id: 'profile', label: 'Profil' }, - { id: 'audit', label: 'Audit' }, - { id: 'documentation', label: 'Dokumentation' }, - ] - - if (loading) { - return ( -
-
-
- ) - } - - return ( -
- {/* Page Purpose */} - - - {/* Stats Overview */} -
-
-
{stats?.total_alerts || 0}
-
Alerts gesamt
-
-
-
{stats?.new_alerts || 0}
-
Neue Alerts
-
-
-
{stats?.kept_alerts || 0}
-
Relevant
-
-
-
{stats?.review_alerts || 0}
-
Zur Pruefung
-
-
- - {/* Tab Navigation */} -
-
- -
- -
- {/* Dashboard Tab */} - {activeTab === 'dashboard' && ( -
- {/* Quick Actions */} -
-
-

Aktive Topics

-
- {topics.slice(0, 5).map((topic) => ( -
-
-
{topic.name}
-
{topic.alert_count} Alerts
-
- - {topic.is_active ? 'Aktiv' : 'Pausiert'} - -
- ))} - {topics.length === 0 && ( -
Keine Topics konfiguriert
- )} -
-
- -
-

Letzte Alerts

-
- {alerts.slice(0, 5).map((alert) => ( -
-
{alert.title}
-
- {alert.topic_name} - {getScoreBadge(alert.relevance_score)} -
-
- ))} - {alerts.length === 0 && ( -
Keine Alerts vorhanden
- )} -
-
-
- - {error && ( -
-

- Hinweis: API nicht erreichbar. Demo-Daten werden angezeigt. -

-
- )} -
- )} - - {/* Inbox Tab */} - {activeTab === 'inbox' && ( -
- {/* Filters */} -
- {['all', 'new', 'keep', 'review'].map((filter) => ( - - ))} -
- - {/* Alerts Table */} -
- - - - - - - - - - - - {filteredAlerts.map((alert) => ( - - - - - - - - ))} - {filteredAlerts.length === 0 && ( - - - - )} - -
AlertTopicScoreDecisionZeit
- - {alert.title} - -

{alert.snippet}

-
{alert.topic_name}{getScoreBadge(alert.relevance_score)}{getDecisionBadge(alert.relevance_decision)}{formatTimeAgo(alert.fetched_at)}
- Keine Alerts gefunden -
-
-
- )} - - {/* Topics Tab */} - {activeTab === 'topics' && ( -
-
-

Feed Topics

- -
- -
- {topics.map((topic) => ( -
-
-
- - - -
- - {topic.is_active ? 'Aktiv' : 'Pausiert'} - -
-

{topic.name}

-

{topic.feed_url}

-
-
- {topic.alert_count} - Alerts -
-
- {formatTimeAgo(topic.last_fetched_at)} -
-
-
- ))} - {topics.length === 0 && ( -
- Keine Topics konfiguriert -
- )} -
-
- )} - - {/* Rules Tab */} - {activeTab === 'rules' && ( -
-
-

Filterregeln

- -
- -
- {rules.map((rule) => ( -
-
- - - -
-
-
{rule.name}
-
- Wenn: {rule.conditions[0]?.field} {rule.conditions[0]?.operator} "{rule.conditions[0]?.value}" -
-
- - {rule.action_type} - -
-
-
-
- ))} - {rules.length === 0 && ( -
- Keine Regeln konfiguriert -
- )} -
-
- )} - - {/* Profile Tab */} - {activeTab === 'profile' && ( -
-
-

Relevanzprofil

- -
-
- -